Take-Home Exercise 1: Geospatial Analytics for Social Good: Application of Spatial and Spatio-temporal Point Patterns Analysis to discover the geographical distribution of Armed Conflict in Myanmar

Author

Georgia Ng

Published

September 5, 2024

Modified

September 21, 2024

1. Overview

1.1 Introduction

Since early 2021, Myanmar has been engulfed in a brutal civil war that erupted following the military coup on February 1, 2021, which ousted the democratically elected government led by Aung San Suu Kyi. The coup ignited widespread protests and a civil disobedience movement across the country, escalating into armed resistance as various ethnic armed groups and newly formed militias began confronting the military junta. The conflict has led to severe humanitarian crises, including widespread violence, displacement, and human rights abuses. The military’s response has been marked by extreme repression, including airstrikes and targeted attacks on civilian populations, exacerbating the suffering of ordinary Myanmar citizens. Despite international condemnation and calls for a return to democratic governance, the violence continues, deepening the political instability and socio-economic challenges faced by Myanmar.

In light of this ongoing crisis, I will be conducting a detailed geospatial analysis of the conflict events in Myanmar. This analysis aims to map and evaluate the spatial distribution and intensity of conflict incidents from 2021 to 2024. By utilizing geospatial techniques and spatial data, I will examine patterns of violence, identify hotspots of conflict, and assess the impact on civilian areas. This approach will provide valuable insights into the spatial dynamics of the conflict, helping to inform humanitarian responses and policy decisions.

1.2 Goal

We will be focusing on 4 event types: Battles, Explosion/Remote violence, Strategic developments, and Violence against civilians.

  • Using appropriate function of sf and tidyverse packages, import and transform the downloaded armed conflict data and administrative boundary data into sf tibble data.frames.

  • Using the geospatial data sets prepared, derive quarterly KDE layers.

  • Using the geospatial data sets prepared, perform 2nd-Order Spatial Point Patterns Analysis.

  • Using the geospatial data sets prepared, derive quarterly spatio-temporal KDE layers.

  • Using the geospatial data sets prepared, perform 2nd-Order Spatio-temporal Point Patterns Analysis.

  • Using appropriate tmap functions, display the KDE and Spatio-temporal KDE layers on openstreetmap of Myanmar.

  • Describe the spatial patterns revealed by the KDE and Spatio-temporal KDE maps.1.1 The Data

1.3 Importing of Packages

Before we start off, we will have to import the necessary packages required for us to conduct our analysis.

We will be using the following packages:

  • sf: Manages spatial vector data, enabling operations like spatial joins, buffering, and transformations for points, lines, and polygons.

  • raster: Handles raster data, allowing for operations such as raster calculations, resampling, and visualization of spatial grids (e.g., elevation or satellite images).

  • spNetwork: Analyzes spatial networks by modeling connectivity and movement within networks like road systems or utility grids.

  • tmap: Creates and customizes thematic maps for spatial data visualization, including static and interactive maps with various map elements.

  • tidyverse: A suite of packages for data manipulation (dplyr), visualization (ggplot2), and tidying (tidyr), facilitating a streamlined workflow for data analysis.

  • RColorBrewer: Provides a range of color palettes for effective and aesthetically pleasing data visualization, including options for categorical, sequential, and diverging data.

  • spatstat: Conducts spatial statistics and analysis, including point pattern analysis and spatial simulations, to study spatial distributions and interactions.

  • sparr: Facilitates the analysis of spatial point patterns and spatial autoregressive models. It includes functions for fitting and analyzing spatial point processes, particularly useful for examining spatial dependencies and interactions in point pattern data.

  • stpp : Provides tools for spatio-temporal point pattern analysis, including methods for modeling and analyzing the interactions and distributions of points over both space and time.

pacman::p_load(sf, raster, spNetwork, tmap, tidyverse, RColorBrewer, spatstat, sparr, stpp,lgcp)

1.4 The Data

For the purpose of this study, we will be using the following datasets. Particularly, I will be focusing on the quarterly armed conflict events from January 2021 until June 2024.

2. Data Wrangling

2.1 ACLED data

2.1.1 Importing Data

The below chunk of code is used to import ACLED conflict data from a CSV file and convert it into a geospatial data frame using longitude and latitude as coordinates.

In order to perform geoprocessing using two geospatial data, we need to ensure that both geospatial data are projected using similar coordinate system which is why in this case we will project it to WGS84 with the crs code of 32647 using st_transform.

Since the column event_date was stored as characters, dmy() is also used to format the event_date column into a standardized date format for further analysis.

st_as_sf is used to convert a data frame or other tabular data (like from CSVs, data frames, or tibbles) into a simple features (sf) object.

acled_sf <- read_csv("data/aspatial/2021-01-01-2024-06-30-Myanmar.csv") %>% 
  st_as_sf(coords = c(
    "longitude", "latitude"), crs = 4326) %>% 
  st_transform(crs= 32647)%>%
  mutate(event_date = dmy(event_date))

Now, let us check the CRS again by using the code chunk below.

st_crs(acled_sf)
Coordinate Reference System:
  User input: EPSG:32647 
  wkt:
PROJCRS["WGS 84 / UTM zone 47N",
    BASEGEOGCRS["WGS 84",
        ENSEMBLE["World Geodetic System 1984 ensemble",
            MEMBER["World Geodetic System 1984 (Transit)"],
            MEMBER["World Geodetic System 1984 (G730)"],
            MEMBER["World Geodetic System 1984 (G873)"],
            MEMBER["World Geodetic System 1984 (G1150)"],
            MEMBER["World Geodetic System 1984 (G1674)"],
            MEMBER["World Geodetic System 1984 (G1762)"],
            MEMBER["World Geodetic System 1984 (G2139)"],
            ELLIPSOID["WGS 84",6378137,298.257223563,
                LENGTHUNIT["metre",1]],
            ENSEMBLEACCURACY[2.0]],
        PRIMEM["Greenwich",0,
            ANGLEUNIT["degree",0.0174532925199433]],
        ID["EPSG",4326]],
    CONVERSION["UTM zone 47N",
        METHOD["Transverse Mercator",
            ID["EPSG",9807]],
        PARAMETER["Latitude of natural origin",0,
            ANGLEUNIT["degree",0.0174532925199433],
            ID["EPSG",8801]],
        PARAMETER["Longitude of natural origin",99,
            ANGLEUNIT["degree",0.0174532925199433],
            ID["EPSG",8802]],
        PARAMETER["Scale factor at natural origin",0.9996,
            SCALEUNIT["unity",1],
            ID["EPSG",8805]],
        PARAMETER["False easting",500000,
            LENGTHUNIT["metre",1],
            ID["EPSG",8806]],
        PARAMETER["False northing",0,
            LENGTHUNIT["metre",1],
            ID["EPSG",8807]]],
    CS[Cartesian,2],
        AXIS["(E)",east,
            ORDER[1],
            LENGTHUNIT["metre",1]],
        AXIS["(N)",north,
            ORDER[2],
            LENGTHUNIT["metre",1]],
    USAGE[
        SCOPE["Engineering survey, topographic mapping."],
        AREA["Between 96°E and 102°E, northern hemisphere between equator and 84°N, onshore and offshore. China. Indonesia. Laos. Malaysia - West Malaysia. Mongolia. Myanmar (Burma). Russian Federation. Thailand."],
        BBOX[0,96,84,102]],
    ID["EPSG",32647]]

2.1.2 Data Pruning and Structuring

2.1.2.1 Extracting Data for the 4 Main Event Types

Since the data we have encapsulates all 6 types of events and we are only focusing on the 4 event types that I have indicated above, we will only be extracting the data related to that.

acled_sf <- acled_sf %>%
  filter(event_type %in% c("Battles", "Strategic developments", "Violence against civilians", "Explosions/Remote violence"))

Using unique(), we can check that only rows that fall under the 4 event types have been kept.

unique(acled_sf$event_type)
[1] "Battles"                    "Strategic developments"    
[3] "Violence against civilians" "Explosions/Remote violence"

2.1.2.2 Data Transformation for Quarterly Analysis

Since we will be analyzing it by quarters, let’s use mutate() to extract the information. Using quarter() from the lubridate package, we can derive the quarters from the event_date.

acled_sf <- acled_sf %>% mutate(quarter = quarter(event_date))

Using colnames() we can see that the new column quarter is added.

colnames(acled_sf) 
 [1] "event_id_cnty"      "event_date"         "year"              
 [4] "time_precision"     "disorder_type"      "event_type"        
 [7] "sub_event_type"     "actor1"             "assoc_actor_1"     
[10] "inter1"             "actor2"             "assoc_actor_2"     
[13] "inter2"             "interaction"        "civilian_targeting"
[16] "iso"                "region"             "country"           
[19] "admin1"             "admin2"             "admin3"            
[22] "location"           "geo_precision"      "source"            
[25] "source_scale"       "notes"              "fatalities"        
[28] "tags"               "timestamp"          "geometry"          
[31] "quarter"           

2.1.2.3 Removal of Redundant Columns

To enhance the efficiency of our dataset, we will also remove redundant columns. This practice reduces memory usage and processing time while simplifying the analysis. By focusing on only the relevant data, we minimize complexity and ensure clearer, more focused results. Examples of the columns we will be removing are: time_precision, inter1 etc. With this, we are able to reduce to a total of 20 columns.

# Define columns to be removed
columns_to_remove <- c("time_precision", "inter1", "inter2", "iso", "source", "source_scale", "notes", "tags", "region", "geo_precision", "source_scale","country")

# Remove columns only if they exist in the dataframe
acled_sf <- acled_sf %>%
  dplyr::select(-all_of(columns_to_remove[columns_to_remove %in% names(acled_sf)]))

This is an overview of all the columns left:

colnames(acled_sf)
 [1] "event_id_cnty"      "event_date"         "year"              
 [4] "disorder_type"      "event_type"         "sub_event_type"    
 [7] "actor1"             "assoc_actor_1"      "actor2"            
[10] "assoc_actor_2"      "interaction"        "civilian_targeting"
[13] "admin1"             "admin2"             "admin3"            
[16] "location"           "fatalities"         "timestamp"         
[19] "geometry"           "quarter"           

2.1.2.4 Grouping of Data By Year, Quarter, Event Type

This chunk of code groups the ACLED Data by quarter, event and year, allowing us to better manage and access the data later on.

grped_acled_sf <- acled_sf %>%
  group_by(year, quarter, event_type) %>%
  summarize(event_count = n(), .groups = 'drop')

2.2 Myanmar Boundary and Sub-Region Dataset from MIMU

The code chunk below uses st_read() function of sf package to import mmr_polbnda2_adm1_250k_mimu_1 shapefile into R as a polygon feature data frame.

mmrsr_sf <- st_read(dsn="data/geospatial/mmr_polbnda2_adm1_250k_mimu_1/", 
                   layer="mmr_polbnda2_adm1_250k_mimu_1")
Reading layer `mmr_polbnda2_adm1_250k_mimu_1' from data source 
  `/Users/georgiaxng/georgiaxng/is415-handson/Take-home_Ex/Take-home_Ex01/data/geospatial/mmr_polbnda2_adm1_250k_mimu_1' 
  using driver `ESRI Shapefile'
Simple feature collection with 18 features and 6 fields
Geometry type: MULTIPOLYGON
Dimension:     XY
Bounding box:  xmin: 92.1721 ymin: 9.696844 xmax: 101.17 ymax: 28.54554
Geodetic CRS:  WGS 84

As shown below, currently the data is in geographic coordinate system (latitude/longitude), we will need to transform it to a projected CRS before proceeding.

st_crs(mmrsr_sf)
Coordinate Reference System:
  User input: WGS 84 
  wkt:
GEOGCRS["WGS 84",
    DATUM["World Geodetic System 1984",
        ELLIPSOID["WGS 84",6378137,298.257223563,
            LENGTHUNIT["metre",1]],
        ID["EPSG",6326]],
    PRIMEM["Greenwich",0,
        ANGLEUNIT["degree",0.0174532925199433],
        ID["EPSG",8901]],
    CS[ellipsoidal,2],
        AXIS["geodetic longitude",east,
            ORDER[1],
            ANGLEUNIT["degree",0.0174532925199433]],
        AXIS["geodetic latitude",north,
            ORDER[2],
            ANGLEUNIT["degree",0.0174532925199433]]]
mmrsr_sf <- st_transform(mmrsr_sf, crs = 32647)
st_crs(mmrsr_sf)
Coordinate Reference System:
  User input: EPSG:32647 
  wkt:
PROJCRS["WGS 84 / UTM zone 47N",
    BASEGEOGCRS["WGS 84",
        ENSEMBLE["World Geodetic System 1984 ensemble",
            MEMBER["World Geodetic System 1984 (Transit)"],
            MEMBER["World Geodetic System 1984 (G730)"],
            MEMBER["World Geodetic System 1984 (G873)"],
            MEMBER["World Geodetic System 1984 (G1150)"],
            MEMBER["World Geodetic System 1984 (G1674)"],
            MEMBER["World Geodetic System 1984 (G1762)"],
            MEMBER["World Geodetic System 1984 (G2139)"],
            ELLIPSOID["WGS 84",6378137,298.257223563,
                LENGTHUNIT["metre",1]],
            ENSEMBLEACCURACY[2.0]],
        PRIMEM["Greenwich",0,
            ANGLEUNIT["degree",0.0174532925199433]],
        ID["EPSG",4326]],
    CONVERSION["UTM zone 47N",
        METHOD["Transverse Mercator",
            ID["EPSG",9807]],
        PARAMETER["Latitude of natural origin",0,
            ANGLEUNIT["degree",0.0174532925199433],
            ID["EPSG",8801]],
        PARAMETER["Longitude of natural origin",99,
            ANGLEUNIT["degree",0.0174532925199433],
            ID["EPSG",8802]],
        PARAMETER["Scale factor at natural origin",0.9996,
            SCALEUNIT["unity",1],
            ID["EPSG",8805]],
        PARAMETER["False easting",500000,
            LENGTHUNIT["metre",1],
            ID["EPSG",8806]],
        PARAMETER["False northing",0,
            LENGTHUNIT["metre",1],
            ID["EPSG",8807]]],
    CS[Cartesian,2],
        AXIS["(E)",east,
            ORDER[1],
            LENGTHUNIT["metre",1]],
        AXIS["(N)",north,
            ORDER[2],
            LENGTHUNIT["metre",1]],
    USAGE[
        SCOPE["Engineering survey, topographic mapping."],
        AREA["Between 96°E and 102°E, northern hemisphere between equator and 84°N, onshore and offshore. China. Indonesia. Laos. Malaysia - West Malaysia. Mongolia. Myanmar (Burma). Russian Federation. Thailand."],
        BBOX[0,96,84,102]],
    ID["EPSG",32647]]

To check that all the spatial objects are valid, st_is_valid() is utilized. As shown below, all of the objects are valid.

st_is_valid(mmrsr_sf)
 [1] TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE
[16] TRUE TRUE TRUE

This is a brief overview of how the map of Myanmar looks like.

plot(mmrsr_sf)

2.3 Mapping the Geospatial data sets

Now, let’s plot the spatial map to gain an initial understanding of the geographic distribution of all of the 4 conflict events by year. As observed, with such a large volume of data, identifying patterns through visual inspection alone can be challenging. This highlights the importance of conducting further analysis to uncover trends and achieve a deeper understanding of the spatial dynamics of these events.

From a brief examination of the map, it’s evident that the region south of Sagaing exhibits the highest concentration of conflicts overall. This area shows a notably higher density of conflict events compared to other regions, marking it as a significant hotspot. However, to fully grasp these patterns and their implications, additional analysis is needed.

# Filter data for 2021
acled_2021 <- acled_sf %>% filter(year == 2021)

# Plot map for 2021
tm_shape(mmrsr_sf) +
  tm_polygons() + 
  tm_shape(acled_2021) + 
  tm_dots() +
  tm_layout(main.title = "ACLED Conflict Events in 2021", main.title.size = 1.2)

# Filter data for 2022
acled_2022 <- acled_sf %>% filter(year == 2022)

# Plot map for 2022
tm_shape(mmrsr_sf) +
  tm_polygons() + 
  tm_shape(acled_2022) + 
  tm_dots() +
  tm_layout(main.title = "ACLED Conflict Events in 2022", main.title.size = 1.2)

# Filter data for 2023
acled_2023 <- acled_sf %>% filter(year == 2023)

# Plot map for 2023
tm_shape(mmrsr_sf) +
  tm_polygons() + 
  tm_shape(acled_2023) + 
  tm_dots() +
  tm_layout(main.title = "ACLED Conflict Events in 2023", main.title.size = 1.2)

# Filter data for 2024
acled_2024 <- acled_sf %>% filter(year == 2024)

# Plot map for 2024
tm_shape(mmrsr_sf) +
  tm_polygons() + 
  tm_shape(acled_2024) + 
  tm_dots() +
  tm_layout(main.title = "ACLED Conflict Events in 2024", main.title.size = 1.2)

2.4 Myanmar Sub-regions: For Reference and Context

For reference to the different sub-regions in Myanmar, we can refer to the labelled map plotted below:

num_colors <- length(unique(mmrsr_sf$ST))
colors <- brewer.pal(n = num_colors, name = "Set3")

tm_shape(mmrsr_sf) +
  tm_polygons(col = "ST", palette = colors) +  # Apply color palette to polygons
  tm_text("ST", size = 1, col = "black", bg.color = "white", just = c("center", "center"),  xmod = 0, ymod = 0) + tm_layout(main.title = "Myanmar",
            main.title.position = "center",
            main.title.size = 1.6,
            legend.outside = TRUE,
            frame = TRUE)+
    tm_legend(title = "Sub-regions")  # Set custom legend title

2.5 Myanmar District Data

This chunk of code imports the geospatial data for the districts of Myanmar from MIMU.

mmrsr_district_sf <- st_read(dsn="data/geospatial/mmr_district_mimu/", 
                   layer="mmr_polbnda_adm2_250k_mimu")
Reading layer `mmr_polbnda_adm2_250k_mimu' from data source 
  `/Users/georgiaxng/georgiaxng/is415-handson/Take-home_Ex/Take-home_Ex01/data/geospatial/mmr_district_mimu' 
  using driver `ESRI Shapefile'
Simple feature collection with 80 features and 7 fields
Geometry type: MULTIPOLYGON
Dimension:     XY
Bounding box:  xmin: 92.1721 ymin: 9.696844 xmax: 101.17 ymax: 28.54554
Geodetic CRS:  WGS 84
mmrsr_district_sf <- st_transform(mmrsr_district_sf, crs = 32647)
plot(mmrsr_district_sf)

2.6 Setting Seed

To ensure the reproducibility, we will be using a fixed seed for our randomisation.

set.seed(123456)

3. Kernel Density Estimation

To analyze and find the trends in the spatial distribution of conflict events in Myanmar, we will be using Kernel Density Estimation (KDE). KDE is a non-parametric way to estimate the probability density function of a random variable, making it particularly useful in spatial analysis for identifying areas with high concentrations of events.

To determine the optimal bandwidth and kernel parameters for KDE, we will be using a sample of the data. By focusing on a smaller subset, such as specific event types or time periods, we can efficiently explore different parameter combinations and choose the one that provides the most meaningful results before scaling the analysis to the entire dataset.

3.1 Creating owin object

To improve the accuracy of our spatial analysis, we will exclude the smaller islands that are too minor to contribute meaningfully to the Kernel Density Estimation (KDE). We will focus on the main island by merging the geometry and selecting the relevant polygon.

In this code snippet, we combine all intersecting geometries in the mmrsr_sf object into a single polygon using st_union(). We then use st_cast() to ensure that the result is a polygon type. From this merged polygon, we select the row with the highest number of features, which typically corresponds to the main island or the most prominent feature in the dataset.

This selected polygon is then converted to a window object using as.owin(), which allows us to use it in spatial analyses, such as Kernel Density Estimation (KDE).

merged_mmr <- st_union(mmrsr_sf) %>%
    st_cast("POLYGON")
merged_mmr <- merged_mmr[c(9)]

mmr_owin <- as.owin(merged_mmr)

Here is the plot of the main island, without its outer islands.

plot(mmr_owin)

3.2 Selection of Sample Data

To choose a representative sample, we will first calculate the median number of data points across different groupings, such as year, quarter, and event type. This will help us understand the typical size of our data subsets. Using this median value, we will identify the subset of data with an event_count closest to this median.

By selecting a sample that aligns with this typical value, we ensure that our sample accurately reflects the general distribution of the dataset. We will then use this representative sample to determine the optimal parameters for Kernel Density Estimation (KDE), such as bandwidth and kernel type. This approach will help us achieve more reliable and meaningful results in our spatial analysis.

Here is the R code to select the representative sample. In this case, the resulting sample data we have arrived at is for “Battles” in the second quarter of 2023.

# Calculate mean and median of event counts
median_event_count <- median(grped_acled_sf$event_count)

# Find the row with event_count closest to the median
sample_acled_sf <- grped_acled_sf %>%
  mutate(distance_to_median = abs(event_count - median_event_count)) %>%
  arrange(distance_to_median) %>%
  slice(1)

# Print the closest row(s)
sample_acled_sf
Simple feature collection with 1 feature and 5 fields
Geometry type: MULTIPOINT
Dimension:     XY
Bounding box:  xmin: -174035.7 ymin: 1103500 xmax: 518300.4 ymax: 3006373
Projected CRS: WGS 84 / UTM zone 47N
# A tibble: 1 × 6
   year quarter event_type event_count                                  geometry
  <dbl>   <int> <chr>            <int>                          <MULTIPOINT [m]>
1  2023       2 Battles            814 ((-174035.7 2284958), (-152179.1 2277258…
# ℹ 1 more variable: distance_to_median <dbl>

Here, we converted the representative sample data to ppp format and checked for duplicates. In this case, no duplicates were found, confirming the integrity of our sample for further KDE analysis.

sample_acled_ppp <- as.ppp(st_coordinates(sample_acled_sf),st_bbox(sample_acled_sf))
any(duplicated(sample_acled_ppp))
[1] FALSE
sample_acled_ppp <- sample_acled_ppp[mmr_owin]

An overview of the point pattern plot for the sample data.

plot(sample_acled_ppp, pch = 16, cex = 0.8) 

3.3 Determining the Optimal Bandwidth & Sigma

3.3.1 Understanding Sigma & Kernel in Kernel Density Estimation (KDE)

Kernel:

The kernel in Kernel Density Estimation (KDE) is a smoothing function that estimates the probability density function of a dataset. The choice of kernel function affects how the smoothing is applied to each data point. Common kernels include:

  • Gaussian Kernel: Assumes a normal distribution around each data point, providing a smooth, continuous density estimate.

  • Epanechnikov Kernel: Parabolic in shape, it minimizes mean squared error but is less smooth than Gaussian.

  • Uniform Kernel: Assumes constant density within a fixed distance around each data point, simpler but less smooth.

  • Triangular Kernel: Offers a clear density representation with sharp edges but less smoothness.

The choice of kernel affects the density estimate’s smoothness and shape, with the Gaussian kernel being a versatile and commonly used option.

Sigma:

The sigma argument in the density() function represents the bandwidth of the kernel in KDE. It controls the level of smoothing applied to the density estimate:

  • What Sigma Represents: Sigma defines the standard deviation of the kernel function, determining the width of the “smoothing window” around each data point.

  • Effect on KDE:

    • Smaller Sigma: Leads to less smoothing, producing a more sensitive and detailed density plot, but can highlight noise.

    • Larger Sigma: Results in greater smoothing, providing a broader view of density but might obscure finer details.

  • Choosing Sigma: An appropriate sigma balances detail and smoothness. Methods like bw.diggle, bw.ppl, and bw.scott can help determine the optimal sigma value.

In summary, the kernel determines the density estimate’s shape, while sigma controls the smoothing level. Both are crucial for accurately reflecting the spatial distribution of data, and selecting the right parameters is essential for meaningful KDE results.

3.3.2 Computing Default Kernel Density Estimation

First, since the original data is in meters, we need to rescale it to kilometers to facilitate more meaningful spatial analysis and visualization. By converting the units to kilometers, we ensure that the scale of the Kernel Density Estimation (KDE) and subsequent analyses align with the geographical extent of the study area.

sample_acled_ppp.km <- rescale.ppp(sample_acled_ppp, 1000, "km")

To visualize the spatial distribution of conflict events, here is the Kernel Density Estimation (KDE) plot generated using the default settings, as shown in the code. While default settings can be sufficient for some analyses, the resulting KDE plot here appears oversmoothed, which may obscure finer details and potential small-scale clusters.

This oversmoothing effect reduces the ability to detect subtle patterns in the conflict events, highlighting the importance of adjusting parameters like bandwidth (sigma) to better capture the underlying spatial structure. Thus, to improve the accuracy and granularity of the KDE, we will need to continue experimenting with different bandwidth values and kernel functions.

par(mar = c(0,1,1,1))
kde_default_destination <- density(sample_acled_ppp.km)
plot(kde_default_destination,main = "Default Density KDE for ACLED Myanmar 2023 Q2 'Battles'")

3.4 Experimenting With Fixed Bandwidth

To effectively choose the appropriate bandwidth for Kernel Density Estimation (KDE), several automatic bandwidth selection methods can be employed. Each method has its own approach and focus, which can significantly impact the resulting density estimate. Below are brief descriptions of four common bandwidth selection methods:

  • bw.CvL: Calculates bandwidth using cross-validation to minimize the integrated mean squared error (IMSE) of the density estimate. This method aims to balance detail and smoothness by optimizing the bandwidth based on a global error metric, which might lead to a broad smoothing effect.

  • bw.scott: Applies a rule-of-thumb bandwidth based on the sample size and dimensionality of the data. This method is often effective for larger datasets, providing a more generalized estimate by assuming isotropic smoothing (uniform smoothing in all directions).

  • bw.ppl: Uses a bandwidth tailored for point pattern analysis, considering the spatial distribution of data points. This approach focuses on capturing local variations and spatial patterns, resulting in a more localized smoothing effect compared to other methods.

  • bw.diggle: Employs a method specifically designed for point patterns to minimize the variance of the density estimate. This results in a smaller bandwidth, leading to a finer and more detailed density plot that may capture small-scale variations but could also emphasize noise.

3.4.1 Automatic Bandwidth Methods

Lets begin by examining the bandwidth values obtained from various automatic bandwidth selection methods using the following code snippet.

bw.ppl(sample_acled_ppp.km)
   sigma 
21.20514 
bw.diggle(sample_acled_ppp.km)
   sigma 
8.459462 
bw.CvL(sample_acled_ppp.km)
   sigma 
90.92206 
bw.scott(sample_acled_ppp.km)
  sigma.x   sigma.y 
 51.29856 133.58599 

Based on the sigma values obtained from the automatic bandwidth calculation methods, we can draw the following inferences about the Kernel Density Estimation (KDE) for the sample data:

  • bw.ppl: sigma = 21.21
    A smaller sigma value suggests a more localized smoothing effect. This method is designed for point pattern analysis and aims to capture spatial patterns with greater precision, highlighting finer details within the data.

  • bw.diggle: sigma = 8.46
    As the smallest sigma value, it implies a very narrow smoothing window. This method focuses on minimizing variance and tends to produce a detailed density plot. However, it may also accentuate noise or small fluctuations in the data.

  • bw.CvL: sigma = 90.92
    This large value indicates a broad smoothing window. Consequently, each data point affects a wide surrounding area, leading to a smoother KDE that may obscure fine details and small-scale variations.

  • bw.scott: sigma.x = 51.30, sigma.y = 133.59
    The method provides different bandwidths for the x and y dimensions, suggesting anisotropic smoothing. The significant difference between these values indicates varying spatial variations in different directions, with a broader smoothing effect along the y-direction.

3.4.2 Plotting KDE for Different Bandwidth & Kernel

To better understand these differences, we will plot the KDE results using each bandwidth value and different kernels. This visual comparison will help us assess how each bandwidth setting and kernel type influences the density estimates, allowing us to determine which combination provides the most meaningful representation of our data.

In practice, choosing the optimal KDE bandwidth is not straightforward, as there is no one-size-fits-all approach. Many studies emphasize that determining the best bandwidth often relies on visually comparing various settings and kernel types to assess their effectiveness and select the most appropriate one for the specific data at hand.

3.4.2.1 Overview of KDE Maps with Gaussian Kernel and Various Bandwidths

Here is an overview of how in general the KDE maps generated with the respective bandwidths looks like using the Gaussian kernel.

kde_diggle <- density(sample_acled_ppp.km, bw.diggle(sample_acled_ppp.km))
kde_CvL <- density(sample_acled_ppp.km, bw.CvL(sample_acled_ppp.km))
kde_scott <- density(sample_acled_ppp.km, bw.scott(sample_acled_ppp.km))
kde_ppl <- density(sample_acled_ppp.km, bw.ppl(sample_acled_ppp.km))

par(mar = c(1,1,1,1.5),mfrow = c(2,2))
plot(kde_diggle,main = "kde_diggle")
plot(kde_CvL,main = "kde_CvL")
plot(kde_scott,main = "kde_scott")
plot(kde_ppl,main = "kde_ppl")

3.4.2.2 Comparing KDE Results Across Different Bandwidths and Kernels

The code chunk below plots different KDE plots for a various different pairings of kernels and bandwidths.

par(mfrow=c(2,2))
plot(density(sample_acled_ppp.km, sigma=bw.ppl, edge=TRUE, kernel="gaussian"), main="Gaussian")
plot(density(sample_acled_ppp.km, sigma=bw.ppl, edge=TRUE, kernel="epanechnikov"), main="Epanechnikov")
plot(density(sample_acled_ppp.km, sigma=bw.ppl, edge=TRUE, kernel="quartic"), main="Quartic")
plot(density(sample_acled_ppp.km, sigma=bw.ppl, edge=TRUE, kernel="disc"), main="Disc")

par(mfrow=c(2,2))
plot(density(sample_acled_ppp.km, sigma=bw.diggle, edge=TRUE, kernel="gaussian"), main="Gaussian")
plot(density(sample_acled_ppp.km, sigma=bw.diggle, edge=TRUE, kernel="epanechnikov"), main="Epanechnikov")
plot(density(sample_acled_ppp.km, sigma=bw.diggle, edge=TRUE, kernel="quartic"), main="Quartic")
plot(density(sample_acled_ppp.km, sigma=bw.diggle, edge=TRUE, kernel="disc"), main="Disc")

par(mfrow=c(2,2))
plot(density(sample_acled_ppp.km, sigma=bw.CvL, edge=TRUE, kernel="gaussian"), main="Gaussian")
plot(density(sample_acled_ppp.km, sigma=bw.CvL, edge=TRUE, kernel="epanechnikov"), main="Epanechnikov")
plot(density(sample_acled_ppp.km, sigma=bw.CvL, edge=TRUE, kernel="quartic"), main="Quartic")
plot(density(sample_acled_ppp.km, sigma=bw.CvL, edge=TRUE, kernel="disc"), main="Disc")

par(mfrow=c(2,2))
plot(density(sample_acled_ppp.km, sigma=bw.scott, edge=TRUE, kernel="gaussian"), main="Gaussian")
plot(density(sample_acled_ppp.km, sigma=bw.scott, edge=TRUE, kernel="epanechnikov"), main="Epanechnikov")
plot(density(sample_acled_ppp.km, sigma=bw.scott, edge=TRUE, kernel="quartic"), main="Quartic")
plot(density(sample_acled_ppp.km, sigma=bw.scott, edge=TRUE, kernel="disc"), main="Disc")

After evaluating the KDE plots, I opted for the Gaussian kernel with the bandwidth provided by bw.ppl because this method provided a more localized smoothing effect, better capturing the spatial patterns in the data. In comparison, the bw.CvL method yielded a much larger bandwidth, leading to excessive smoothing, while bw.scott showed considerable disparity between x and y dimensions, suggesting anisotropic smoothing. The bw.diggle method, though precise, resulted in very narrow smoothing that might overemphasize noise.

Despite this, I found that the Gaussian KDE with bw.ppl still appeared to be undersmoothed. Consequently, further optimization is necessary to refine the bandwidth setting and achieve a more balanced density estimate that accurately represents the underlying spatial patterns in the data.


3.4.3 Fine-Tuning KDE Parameters

Since the Gaussian KDE using bw.ppl seems to be undersmoothed, we will need to adjust the sigma to achieve the desired results. To address this undersmoothing issue, we can increase the sigma value to widen the smoothing window, as demonstrated below. Through continued experimentation, we have determined these final parameters.

adjusted_bw <- bw.ppl(sample_acled_ppp.km) * 1.5
kde_adjusted <- density(sample_acled_ppp.km, sigma=adjusted_bw, edge=TRUE, kernel="gaussian")
plot(kde_adjusted)

Here is a comparison between the point pattern and the KDE plot of the sample data, this comparison allows us to evaluate how effectively the KDE captures the spatial distribution of the events and highlights areas where the KDE might need further refinement.

par(mar = c(1,0,1,0))
par(mfrow=c(1,2))
plot(sample_acled_ppp, pch = 16, cex = 0.5) 
plot(kde_adjusted)

Value of the adjusted bandwidth:

adjusted_bw
   sigma 
31.80771 

4. Quarterly KDE Layers

4.1 2021

4.1.1 Q1

This chunk of code generates the KDE layers using the adjusted bandwidth we have previously counted and the gaussian kernel, plotting the resulting KDE layers by each quarter.

  par(mar = c(1,0,1,0))
  par(mfrow=c(2,2))
  current_year = 2021
  current_quarter = 1
  quarter_data <- grped_acled_sf %>% filter(year == current_year, quarter == current_quarter) 
  # Loop through each event
  for (i in seq_len(nrow(quarter_data))) {
    
    filtered_sf <- quarter_data[i, ]

    # Extract the current year, quarter, and event type
    current_event <- filtered_sf$event_type
    
    # Convert to ppp object
    filtered_ppp <- as.ppp(st_coordinates(filtered_sf), st_bbox(filtered_sf))
    
    if(any(duplicated(filtered_ppp))){
      rjitter(filtered_ppp, retry=TRUE, nsim=1, drop=TRUE)
    }
    filtered_ppp <- filtered_ppp[mmr_owin]
    # Rescale to kilometres
    filtered_ppp.km <- rescale.ppp(filtered_ppp, 1000, "km")
  
    # Conduct KDE with the chosen bandwidth
    kde_result <- density(filtered_ppp.km, sigma = adjusted_bw, edge = TRUE, kernel = "gaussian")
    
    # Plot the KDE
    plot(kde_result, main = paste(current_event," ",current_year, " Q", current_quarter, sep = ""))
}

Note
  • Battles: Distinct hotspot in Shan (North), which indicates this is likely the main battlefield during this quarter.

  • Explosions/Remote Violence: Appears to have a wider spatial distribution, with more intense activity seen along the southern, western and central-eastern parts of the country. This suggests that attacks carried out here were more widespread and not confined to one region. This could imply different actors using remote violence across various territories or a strategic shift towards this type of warfare in multiple conflict zones.

  • Strategic Developments, Violence Against Civilians: We can observe that both events display a similar KDE map, indicating that both events could have been carried out concurrently, hence leading to the similarity. The co-occurrence of strategic developments and civilian violence is concerning and could imply that the areas experiencing strategic military activity are also sites where civilians are more vulnerable to harm. These could be areas under military occupation, zones of forced displacement, or regions where there is a breakdown of law and order.

4.1.2 Q2

Click to view the code
  par(mar = c(1,0,1,0))
  par(mfrow=c(2,2))
  current_year = 2021
  current_quarter = 2
  quarter_data <- grped_acled_sf %>% filter(year == current_year, quarter == current_quarter) 
  # Loop through each event
  for (i in seq_len(nrow(quarter_data))) {
    
    filtered_sf <- quarter_data[i, ]

    # Extract the current year, quarter, and event type
    current_event <- filtered_sf$event_type
    
    # Convert to ppp object
    filtered_ppp <- as.ppp(st_coordinates(filtered_sf), st_bbox(filtered_sf))
    
    if(any(duplicated(filtered_ppp))){
      rjitter(filtered_ppp, retry=TRUE, nsim=1, drop=TRUE)
    }
    filtered_ppp <- filtered_ppp[mmr_owin]
    # Rescale to kilometres
    filtered_ppp.km <- rescale.ppp(filtered_ppp, 1000, "km")
  
    # Conduct KDE with the chosen bandwidth
    kde_result <- density(filtered_ppp.km, sigma = adjusted_bw, edge = TRUE, kernel = "gaussian")
    
    # Plot the KDE
    plot(kde_result, main = paste(current_event," ",current_year, " Q", current_quarter, sep = ""))
}

Note

* Battles: Presents a more spatially distributed distribution, with new hotspots at the Chin/Sagaing/Magway intersection.

* Explosions/Remote Violence: The intensity of explosions and remote violence has shifted southward, with the most significant hotspot now in Yangon, the capital of the country. The shift to Yangon could also be a sign of more strategic attacks aimed at disrupting economic or political centers of power, following the military coup in early 2021.

* Strategic Developments, Violence Against Civilians: Both events still present similar spatial distribution, reinforcing the idea that military strategic moves are closely linked to targeted violence against civilians.

4.1.3 Q3

Click to view the code
  par(mar = c(1,0,1,0))
  par(mfrow=c(2,2))
  current_year = 2021
  current_quarter = 3
  quarter_data <- grped_acled_sf %>% filter(year == current_year, quarter == current_quarter) 
  # Loop through each event
  for (i in seq_len(nrow(quarter_data))) {
    
    filtered_sf <- quarter_data[i, ]

    # Extract the current year, quarter, and event type
    current_event <- filtered_sf$event_type
    
    # Convert to ppp object
    filtered_ppp <- as.ppp(st_coordinates(filtered_sf), st_bbox(filtered_sf))
    
    if(any(duplicated(filtered_ppp))){
      rjitter(filtered_ppp, retry=TRUE, nsim=1, drop=TRUE)
    }
    filtered_ppp <- filtered_ppp[mmr_owin]
    # Rescale to kilometres
    filtered_ppp.km <- rescale.ppp(filtered_ppp, 1000, "km")
  
    # Conduct KDE with the chosen bandwidth
    kde_result <- density(filtered_ppp.km, sigma = adjusted_bw, edge = TRUE, kernel = "gaussian")
    
    # Plot the KDE
    plot(kde_result, main = paste(current_event," ",current_year, " Q", current_quarter, sep = ""))
}

Note

All 4 events shows a distinct hotspot at southern Sagaing, which appears to be the main battlefield and where most of the conflicts happen for 2021 Q4. Similarly, Yangon appears to have a significant amount of conflicts happening as well.

4.1.4 Q4

Click to view the code
  par(mar = c(1,0,1,0))
  par(mfrow=c(2,2))
  current_year = 2021
  current_quarter = 4
  quarter_data <- grped_acled_sf %>% filter(year == current_year, quarter == current_quarter) 
  # Loop through each event
  for (i in seq_len(nrow(quarter_data))) {
    
    filtered_sf <- quarter_data[i, ]

    # Extract the current year, quarter, and event type
    current_event <- filtered_sf$event_type
    
    # Convert to ppp object
    filtered_ppp <- as.ppp(st_coordinates(filtered_sf), st_bbox(filtered_sf))
    
    if(any(duplicated(filtered_ppp))){
      rjitter(filtered_ppp, retry=TRUE, nsim=1, drop=TRUE)
    }
    filtered_ppp <- filtered_ppp[mmr_owin]
    # Rescale to kilometres
    filtered_ppp.km <- rescale.ppp(filtered_ppp, 1000, "km")
  
    # Conduct KDE with the chosen bandwidth
    kde_result <- density(filtered_ppp.km, sigma = adjusted_bw, edge = TRUE, kernel = "gaussian")
    
    # Plot the KDE
    plot(kde_result, main = paste(current_event," ",current_year, " Q", current_quarter, sep = ""))
}

4.1.5 Overall Insights for 2021

Fluid Conflict Zones: The hotspots of conflict in Myanmar were more fluid and dynamic, with notable shifts in violence across various regions throughout the year. This shifting pattern likely reflects the early stages of the Myanmar civil conflict, which began in full force following the February 2021 military coup. There is a strong focus placed on Yangon and Southern Sagaing in particular.

4.2 2022

4.2.1 Q1

Click to view the code
  par(mar = c(1,0,1,0))
  par(mfrow=c(2,2))
  current_year = 2022
  current_quarter = 1
  quarter_data <- grped_acled_sf %>% filter(year == current_year, quarter == current_quarter) 
  # Loop through each event
  for (i in seq_len(nrow(quarter_data))) {
    
    filtered_sf <- quarter_data[i, ]

    # Extract the current year, quarter, and event type
    current_event <- filtered_sf$event_type
    
    # Convert to ppp object
    filtered_ppp <- as.ppp(st_coordinates(filtered_sf), st_bbox(filtered_sf))
    
    if(any(duplicated(filtered_ppp))){
      rjitter(filtered_ppp, retry=TRUE, nsim=1, drop=TRUE)
    }
    filtered_ppp <- filtered_ppp[mmr_owin]
    # Rescale to kilometres
    filtered_ppp.km <- rescale.ppp(filtered_ppp, 1000, "km")
  
    # Conduct KDE with the chosen bandwidth
    kde_result <- density(filtered_ppp.km, sigma = adjusted_bw, edge = TRUE, kernel = "gaussian")
    
    # Plot the KDE
    plot(kde_result, main = paste(current_event," ",current_year, " Q", current_quarter, sep = ""))
}

4.2.2 Q2

Click to view the code
  par(mar = c(1,0,1,0))
  par(mfrow=c(2,2))
  current_year = 2022
  current_quarter = 2
  quarter_data <- grped_acled_sf %>% filter(year == current_year, quarter == current_quarter) 
  # Loop through each event
  for (i in seq_len(nrow(quarter_data))) {
    
    filtered_sf <- quarter_data[i, ]

    # Extract the current year, quarter, and event type
    current_event <- filtered_sf$event_type
    
    # Convert to ppp object
    filtered_ppp <- as.ppp(st_coordinates(filtered_sf), st_bbox(filtered_sf))
    
    if(any(duplicated(filtered_ppp))){
      rjitter(filtered_ppp, retry=TRUE, nsim=1, drop=TRUE)
    }
    filtered_ppp <- filtered_ppp[mmr_owin]
    # Rescale to kilometres
    filtered_ppp.km <- rescale.ppp(filtered_ppp, 1000, "km")
  
    # Conduct KDE with the chosen bandwidth
    kde_result <- density(filtered_ppp.km, sigma = adjusted_bw, edge = TRUE, kernel = "gaussian")
    
    # Plot the KDE
    plot(kde_result, main = paste(current_event," ",current_year, " Q", current_quarter, sep = ""))
}

4.2.3 Q3

Click to view the code
  par(mar = c(1,0,1,0))
  par(mfrow=c(2,2))
  current_year = 2022
  current_quarter = 3
  quarter_data <- grped_acled_sf %>% filter(year == current_year, quarter == current_quarter) 
  # Loop through each event
  for (i in seq_len(nrow(quarter_data))) {
    
    filtered_sf <- quarter_data[i, ]

    # Extract the current year, quarter, and event type
    current_event <- filtered_sf$event_type
    
    # Convert to ppp object
    filtered_ppp <- as.ppp(st_coordinates(filtered_sf), st_bbox(filtered_sf))
    
    if(any(duplicated(filtered_ppp))){
      rjitter(filtered_ppp, retry=TRUE, nsim=1, drop=TRUE)
    }
    filtered_ppp <- filtered_ppp[mmr_owin]
    # Rescale to kilometres
    filtered_ppp.km <- rescale.ppp(filtered_ppp, 1000, "km")
  
    # Conduct KDE with the chosen bandwidth
    kde_result <- density(filtered_ppp.km, sigma = adjusted_bw, edge = TRUE, kernel = "gaussian")
    
    # Plot the KDE
    plot(kde_result, main = paste(current_event," ",current_year, " Q", current_quarter, sep = ""))
}

4.2.4 Q4

Click to view the code
  par(mar = c(1,0,1,0))
  par(mfrow=c(2,2))
  current_year = 2022
  current_quarter = 4
  quarter_data <- grped_acled_sf %>% filter(year == current_year, quarter == current_quarter) 
  # Loop through each event
  for (i in seq_len(nrow(quarter_data))) {
    
    filtered_sf <- quarter_data[i, ]

    # Extract the current year, quarter, and event type
    current_event <- filtered_sf$event_type
    
    # Convert to ppp object
    filtered_ppp <- as.ppp(st_coordinates(filtered_sf), st_bbox(filtered_sf))
    
    if(any(duplicated(filtered_ppp))){
      rjitter(filtered_ppp, retry=TRUE, nsim=1, drop=TRUE)
    }
    filtered_ppp <- filtered_ppp[mmr_owin]
    # Rescale to kilometres
    filtered_ppp.km <- rescale.ppp(filtered_ppp, 1000, "km")
  
    # Conduct KDE with the chosen bandwidth
    kde_result <- density(filtered_ppp.km, sigma = adjusted_bw, edge = TRUE, kernel = "gaussian")
    
    # Plot the KDE
    plot(kde_result, main = paste(current_event," ",current_year, " Q", current_quarter, sep = ""))
}

4.2.5 Overall Insights for 2022

Stabilization of Conflict Zones: The KDE plot for all quarters for the year of 2022 appears to stay identical, with majority of the conflicts being in the vicinity of southern Sagaing. This consistency indicates a stabilization of the conflict, with fewer fluctuations in the geography of violence. This suggests that the main battleground has become more fixed, with both government forces and resistance groups focusing their efforts in this region.

Implications: The entrenchment of conflict in southern Sagaing also likely reflects a combination of military control over key areas and entrenched resistance by local groups. This stabilization might also indicate a stalemate or protracted conflict, where neither side is able to decisively push the front lines or gain control of new territories.

4.3 2023

4.3.1 Q1

Click to view the code
  par(mar = c(1,0,1,0))
  par(mfrow=c(2,2))
  current_year = 2023
  current_quarter = 1
  quarter_data <- grped_acled_sf %>% filter(year == current_year, quarter == current_quarter) 
  # Loop through each event
  for (i in seq_len(nrow(quarter_data))) {
    
    filtered_sf <- quarter_data[i, ]

    # Extract the current year, quarter, and event type
    current_event <- filtered_sf$event_type
    
    # Convert to ppp object
    filtered_ppp <- as.ppp(st_coordinates(filtered_sf), st_bbox(filtered_sf))
    
    if(any(duplicated(filtered_ppp))){
      rjitter(filtered_ppp, retry=TRUE, nsim=1, drop=TRUE)
    }
    filtered_ppp <- filtered_ppp[mmr_owin]
    # Rescale to kilometres
    filtered_ppp.km <- rescale.ppp(filtered_ppp, 1000, "km")
  
    # Conduct KDE with the chosen bandwidth
    kde_result <- density(filtered_ppp.km, sigma = adjusted_bw, edge = TRUE, kernel = "gaussian")
    
    # Plot the KDE
    plot(kde_result, main = paste(current_event," ",current_year, " Q", current_quarter, sep = ""))
}

4.3.2 Q2

Click to view the code
  par(mar = c(1,0,1,0))
  par(mfrow=c(2,2))
  current_year = 2023
  current_quarter = 2
  quarter_data <- grped_acled_sf %>% filter(year == current_year, quarter == current_quarter) 
  # Loop through each event
  for (i in seq_len(nrow(quarter_data))) {
    
    filtered_sf <- quarter_data[i, ]

    # Extract the current year, quarter, and event type
    current_event <- filtered_sf$event_type
    
    # Convert to ppp object
    filtered_ppp <- as.ppp(st_coordinates(filtered_sf), st_bbox(filtered_sf))
    
    if(any(duplicated(filtered_ppp))){
      rjitter(filtered_ppp, retry=TRUE, nsim=1, drop=TRUE)
    }
    filtered_ppp <- filtered_ppp[mmr_owin]
    # Rescale to kilometres
    filtered_ppp.km <- rescale.ppp(filtered_ppp, 1000, "km")
  
    # Conduct KDE with the chosen bandwidth
    kde_result <- density(filtered_ppp.km, sigma = adjusted_bw, edge = TRUE, kernel = "gaussian")
    
    # Plot the KDE
    plot(kde_result, main = paste(current_event," ",current_year, " Q", current_quarter, sep = ""))
}

4.3.3 Q3

Click to view the code
  par(mar = c(1,0,1,0))
  par(mfrow=c(2,2))
  current_year = 2023
  current_quarter = 3
  quarter_data <- grped_acled_sf %>% filter(year == current_year, quarter == current_quarter) 
  # Loop through each event
  for (i in seq_len(nrow(quarter_data))) {
    
    filtered_sf <- quarter_data[i, ]

    # Extract the current year, quarter, and event type
    current_event <- filtered_sf$event_type
    
    # Convert to ppp object
    filtered_ppp <- as.ppp(st_coordinates(filtered_sf), st_bbox(filtered_sf))
    
    if(any(duplicated(filtered_ppp))){
      rjitter(filtered_ppp, retry=TRUE, nsim=1, drop=TRUE)
    }
    filtered_ppp <- filtered_ppp[mmr_owin]
    # Rescale to kilometres
    filtered_ppp.km <- rescale.ppp(filtered_ppp, 1000, "km")
  
    # Conduct KDE with the chosen bandwidth
    kde_result <- density(filtered_ppp.km, sigma = adjusted_bw, edge = TRUE, kernel = "gaussian")
    
    # Plot the KDE
    plot(kde_result, main = paste(current_event," ",current_year, " Q", current_quarter, sep = ""))
}

4.3.4 Q4

Click to view the code
  par(mar = c(1,0,1,0))
  par(mfrow=c(2,2))
  current_year = 2023
  current_quarter = 4
  quarter_data <- grped_acled_sf %>% filter(year == current_year, quarter == current_quarter) 
  # Loop through each event
  for (i in seq_len(nrow(quarter_data))) {
    
    filtered_sf <- quarter_data[i, ]

    # Extract the current year, quarter, and event type
    current_event <- filtered_sf$event_type
    
    # Convert to ppp object
    filtered_ppp <- as.ppp(st_coordinates(filtered_sf), st_bbox(filtered_sf))
    
    if(any(duplicated(filtered_ppp))){
      rjitter(filtered_ppp, retry=TRUE, nsim=1, drop=TRUE)
    }
    filtered_ppp <- filtered_ppp[mmr_owin]
    # Rescale to kilometres
    filtered_ppp.km <- rescale.ppp(filtered_ppp, 1000, "km")
  
    # Conduct KDE with the chosen bandwidth
    kde_result <- density(filtered_ppp.km, sigma = adjusted_bw, edge = TRUE, kernel = "gaussian")
    
    # Plot the KDE
    plot(kde_result, main = paste(current_event," ",current_year, " Q", current_quarter, sep = ""))
}

4.3.5 Overall Insights for 2023

Consistent hotspots: There’s a persistent area of high activity in the region around Southern Sagaing across all event types and quarters, similar to the previous year.

Event type variations: Different event types show slightly different patterns. For example, “Strategic developments” tend to be more concentrated, while “Battles” and “Explosions/Remote violence” show more widespread activity.

Geographical spread: Activity is not uniform across the country. The southern peninsular region generally shows less activity, while the central and northern regions show more.

Q4 escalation: There appears to be an increase in activity across all event types in Q4, particularly in the northern regions.

4.4 2024

4.4.1 Q1

Click to view the code
  par(mar = c(1,0,1,0))
  par(mfrow=c(2,2))
  current_year = 2024
  current_quarter = 1
  quarter_data <- grped_acled_sf %>% filter(year == current_year, quarter == current_quarter) 
  # Loop through each event
  for (i in seq_len(nrow(quarter_data))) {
    
    filtered_sf <- quarter_data[i, ]

    # Extract the current year, quarter, and event type
    current_event <- filtered_sf$event_type
    
    # Convert to ppp object
    filtered_ppp <- as.ppp(st_coordinates(filtered_sf), st_bbox(filtered_sf))
    
    if(any(duplicated(filtered_ppp))){
      rjitter(filtered_ppp, retry=TRUE, nsim=1, drop=TRUE)
    }
    filtered_ppp <- filtered_ppp[mmr_owin]
    # Rescale to kilometres
    filtered_ppp.km <- rescale.ppp(filtered_ppp, 1000, "km")
  
    # Conduct KDE with the chosen bandwidth
    kde_result <- density(filtered_ppp.km, sigma = adjusted_bw, edge = TRUE, kernel = "gaussian")
    
    # Plot the KDE
    plot(kde_result, main = paste(current_event," ",current_year, " Q", current_quarter, sep = ""))
}

Note

Less conflicts: Conflict events appears to have lessen significantly, as evidenced from the decrease in the intensity level of the hotspots across Myanmar. This decline is particularly apparent for “Explosions/Remote Violence,” where the once prominent hotspots have become less concentrated and more dispersed compared to previous quarters.

4.4.2 Q2

Click to view the code
  par(mar = c(1,0,1,0))
  par(mfrow=c(2,2))
  current_year = 2024
  current_quarter = 2
  quarter_data <- grped_acled_sf %>% filter(year == current_year, quarter == current_quarter) 
  # Loop through each event
  for (i in seq_len(nrow(quarter_data))) {
    
    filtered_sf <- quarter_data[i, ]

    # Extract the current year, quarter, and event type
    current_event <- filtered_sf$event_type
    
    # Convert to ppp object
    filtered_ppp <- as.ppp(st_coordinates(filtered_sf), st_bbox(filtered_sf))
    
    if(any(duplicated(filtered_ppp))){
      rjitter(filtered_ppp, retry=TRUE, nsim=1, drop=TRUE)
    }
    filtered_ppp <- filtered_ppp[mmr_owin]
    # Rescale to kilometres
    filtered_ppp.km <- rescale.ppp(filtered_ppp, 1000, "km")
  
    # Conduct KDE with the chosen bandwidth
    kde_result <- density(filtered_ppp.km, sigma = adjusted_bw, edge = TRUE, kernel = "gaussian")
    
    # Plot the KDE
    plot(kde_result, main = paste(current_event," ",current_year, " Q", current_quarter, sep = ""))
}

Note

Battles: Hotspot in southern Sagaing has decreased in intensity

Violence Against Civilians: Hotspot in Sagaing remains, new hotspots are also appearing on the southern part of Myanmar.

5. 2nd-Order Spatial Point Patterns Analysis

In this section, we will conduct second-order spatial point pattern analysis (PPA), which examines interactions between events at various spatial scales. By using statistical functions such as G, F, K, and L, we can quantify and explore clustering patterns, distances between events, and the spatial distribution of points relative to a given area. These functions help reveal whether the events exhibit random, clustered, or regular patterns, providing deeper insights into the underlying spatial structure of conflict events in Myanmar.

Here’s a detailed description of the second-order spatial point pattern analysis functions:

  • G-function (Nearest Neighbor Distribution Function): The G-function describes the cumulative distribution of the distances from each event to its nearest neighboring event. It highlights how clustered events are by comparing observed distances with those expected from a random spatial pattern.

  • F-function (Empty Space Function): The F-function examines the distribution of distances from arbitrary (random) locations to the nearest event. It helps in evaluating how well events fill the study area. A large gap between random points and events indicates spatial heterogeneity or clustering.

  • K-function (Ripley’s K-function): This function estimates the number of events within a certain distance of each other, relative to what would be expected under complete spatial randomness (CSR). It accumulates the number of events within varying distances, thus detecting clustering or dispersion at multiple spatial scales. If the observed K-function is higher than expected under CSR, it indicates clustering.

  • L-function (Besag’s L-function): The L-function is a variance-stabilized transformation of the K-function. It linearizes the expected K-values, making it easier to interpret deviations from randomness. An L-function above the CSR line indicates clustering, while values below suggest dispersion.

5.1 Analysis Background

For the analysis for the whole of Myanmar, I will be primarily using the G-function to conduct second-order spatial point pattern analysis. I chose it for its computational efficiency, straightforward interpretation of nearest-neighbor distances, and suitability for analyzing a single type of event. The K and L-functions, while valuable, are more resource-intensive, making them not ideal for my large dataset, while the F-function is less applicable to our single-type event analysis.

For the analysis of Sagaing however, I will be using the K-function to conduct the relevant analysis.

5.1.1 Monte Carlo Simulation

I will also be conducting a Monte Carlo simulation for testing Complete Spatial Randomness (CSR) in the spatial distribution of conflict events. This involves generating multiple random point patterns under CSR to create a range of expected spatial statistics. By comparing the observed G-function of the conflict events against these simulated envelopes, I can determine if the observed clustering or dispersion is significantly different from what would be expected by chance.

The envelope function provides upper and lower bounds for the G-function, helping to assess whether the spatial pattern of conflict events is significantly clustered or dispersed compared to random patterns. This method ensures a rigorous evaluation of spatial randomness and enhances the interpretation of observed patterns.

5.1.2 Example of G-Function Plot

This code snippet below provides an example of a G-function plot of a sample of the data, calculated using an envelope derived from 99 simulations. We will also be keeping the confidence level of the envelopes to the default 95%.

This approach is used to evaluate the spatial distribution of events and assess how the observed point pattern compares to what might be expected under complete spatial randomness. Note that we will be using a set seed to ensure the reproducibility of our code.

set.seed(123456)
test_second_sf <- grped_acled_sf %>% filter(event_type == "Battles", year == 2023, quarter ==2)
test_second_ppp<-as.ppp(st_coordinates(test_second_sf), st_bbox(test_second_sf))

if(any(duplicated(test_second_ppp))){
  test_second_ppp <- rjitter(test_second_ppp, retry=TRUE, nsim=1, drop=TRUE)
}

test_second_ppp <- test_second_ppp[mmr_owin]
test_second.csr <- envelope(test_second_ppp, Gest, nsim = 99)
Generating 99 simulations of CSR  ...
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20,
21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60,
61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80,
81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 
99.

Done.
plot(test_second.csr, xlim=c(0,10000), main="Example of G-Function Plot")

Tip

The plot typically shows several key elements related to both the observed data and the simulated patterns:

  • G^obs (Observed G-function):This represents the empirical G-function calculated from your actual point pattern data. It shows the cumulative distribution of the nearest-neighbor distances between points in the dataset.

  • G^theo (Theoretical G-function): Theoretical G-function that would be expected if the point pattern follows Complete Spatial Randomness (CSR). Under CSR, the points are distributed independently and uniformly across the study area.

  • G^hi (Upper Envelope):

    • Upper bound of the G-function values generated from the random simulations (based on nsim, the number of simulations). It defines the maximum value that the G-function could take under CSR in these simulations.

    • If the observed G-function (G^obs) exceeds G^hi, it suggests clustering—points are closer together than expected by chance.

  • G^lo (Lower Envelope):

    • Lower bound of the G-function values from the random simulations. It defines the minimum G-function value expected under CSR.

    • If the observed G-function (G^obs) falls below G^lo, it suggests dispersion or regularity—points are more evenly spaced than expected by chance.

  • Shaded area: Represents the 95% confidence interval

5.2 Whole of Myanmar

5.2.1 Hypothesis

To confirm the observed spatial patterns, I will conduct a hypothesis test using the G-function along with Monte Carlo simulation envelopes. The test will be designed as follows:

  • H₀: The distribution of conflict events in Myanmar is randomly distributed (follows Complete Spatial Randomness, CSR).

  • H₁: The distribution of conflict events in Myanmar is not randomly distributed.

  • If the observed G-function lies outside the envelope, we will reject the null hypothesis, suggesting the presence of spatial clustering or dispersion.

  • A 95% confidence interval will be used for the envelopes.

5.2.2 Battles

This chunk of code generates a G-function plot for all quarters of a particular event, using an envelope derived from 99 simulations for significance testing. Then, it saves the results to a list to plot it later on.

set.seed(123456)
battle_data_sf <- grped_acled_sf %>% filter(event_type == "Battles")

# Initialize an empty list to store the results
battle_G_CK_list <- list()

# Loop through each combination of year and quarter
for (i in seq_len(nrow(battle_data_sf))) {
  
  # Filter the data for the current iteration
  filtered_sf <- battle_data_sf[i, ]
  
  # Extract the current year, quarter, and event type
  current_year <- filtered_sf$year
  current_quarter <- filtered_sf$quarter
  
  # Convert to ppp object
  filtered_ppp <- as.ppp(st_coordinates(filtered_sf), st_bbox(filtered_sf))
  
  if(any(duplicated(filtered_ppp))){
    filtered_ppp <- rjitter(filtered_ppp, retry=TRUE, nsim=1, drop=TRUE)
  }
  
  filtered_ppp <- filtered_ppp[mmr_owin]
  G_CK.csr <- envelope(filtered_ppp, Gest, nsim = 99)
  
  # Save the result in the list with a unique name
  name <- paste0(current_year, "_Q", current_quarter)
  battle_G_CK_list[[name]] <- G_CK.csr
}

write_rds(battle_G_CK_list, "data/rds/battle_G_CK_list.rds")

This chunk of code plots the G-function plot for all quarters.

par(mar = c(2,1,2,1))
par(mfrow=c(4,4))
# Plotting
for(i in seq_along(battle_G_CK_list)) {
  g_ck <- battle_G_CK_list[[i]]
  plot(g_ck, xlim=c(0,10000), main=names(battle_G_CK_list)[i])
}

5.2.3 Explosions/Remote Violence

Click to view the code
set.seed(123456)
explosions_data_sf <- grped_acled_sf %>% filter(event_type == "Explosions/Remote violence")

# Initialize an empty list to store the results
explosions_G_CK_list <- list()

# Loop through each combination of year and quarter
for (i in seq_len(nrow(explosions_data_sf))) {
  
  # Filter the data for the current iteration
  filtered_sf <- explosions_data_sf[i, ]
  
  # Extract the current year, quarter, and event type
  current_year <- filtered_sf$year
  current_quarter <- filtered_sf$quarter
  
  # Convert to ppp object
  filtered_ppp <- as.ppp(st_coordinates(filtered_sf), st_bbox(filtered_sf))
  
  if(any(duplicated(filtered_ppp))){
    filtered_ppp <- rjitter(filtered_ppp, retry=TRUE, nsim=1, drop=TRUE)
  }
  
  filtered_ppp <- filtered_ppp[mmr_owin]
  G_CK.csr <- envelope(filtered_ppp, Gest, nsim = 99)
  
  # Save the result in the list with a unique name
  name <- paste0(current_year, "_Q", current_quarter)
  explosions_G_CK_list[[name]] <- G_CK.csr
}
write_rds(explosions_G_CK_list, "data/rds/explosions_G_CK_list.rds")
par(mar = c(2,1,2,1))
par(mfrow=c(4,4))
# Plotting
for(i in seq_along(explosions_G_CK_list)) {
  g_ck <- explosions_G_CK_list[[i]]
  plot(g_ck, xlim=c(0,10000), main=names(explosions_G_CK_list)[i])
}

5.2.4 Strategic Developments

Click to view the code
set.seed(123456)
strategic_data_sf <- grped_acled_sf %>% filter(event_type == "Strategic developments")

# Initialize an empty list to store the results
strategic_G_CK_list <- list()

# Loop through each combination of year and quarter
for (i in seq_len(nrow(strategic_data_sf))) {
  
  # Filter the data for the current iteration
  filtered_sf <- strategic_data_sf[i, ]
  
  # Extract the current year, quarter, and event type
  current_year <- filtered_sf$year
  current_quarter <- filtered_sf$quarter
  
  # Convert to ppp object
  filtered_ppp <- as.ppp(st_coordinates(filtered_sf), st_bbox(filtered_sf))
  
  if(any(duplicated(filtered_ppp))){
    filtered_ppp <- rjitter(filtered_ppp, retry=TRUE, nsim=1, drop=TRUE)
  }
  
  filtered_ppp <- filtered_ppp[mmr_owin]
  G_CK.csr <- envelope(filtered_ppp, Gest, nsim = 99)
  
  # Save the result in the list with a unique name
  name <- paste0(current_year, "_Q", current_quarter)
  strategic_G_CK_list[[name]] <- G_CK.csr
}
write_rds(strategic_G_CK_list, "data/rds/strategic_G_CK_list.rds")
par(mar = c(2,1,2,1))
par(mfrow=c(4,4))
# Plotting
for(i in seq_along(strategic_G_CK_list)) {
  g_ck <- strategic_G_CK_list[[i]]
  plot(g_ck, xlim=c(0,10000), main=names(strategic_G_CK_list)[i])
}

5.2.5 Violence Against Civilians

Click to view the code
set.seed(123456)
violence_data_sf <- grped_acled_sf %>% filter(event_type == "Violence against civilians")

# Initialize an empty list to store the results
violence_G_CK_list <- list()

# Loop through each combination of year and quarter
for (i in seq_len(nrow(violence_data_sf))) {
  
  # Filter the data for the current iteration
  filtered_sf <- violence_data_sf[i, ]
  
  # Extract the current year, quarter, and event type
  current_year <- filtered_sf$year
  current_quarter <- filtered_sf$quarter
  
  # Convert to ppp object
  filtered_ppp <- as.ppp(st_coordinates(filtered_sf), st_bbox(filtered_sf))
  
  if(any(duplicated(filtered_ppp))){
    filtered_ppp <- rjitter(filtered_ppp, retry=TRUE, nsim=1, drop=TRUE)
  }
  
  filtered_ppp <- filtered_ppp[mmr_owin]
  G_CK.csr <- envelope(filtered_ppp, Gest, nsim = 99)
  
  # Save the result in the list with a unique name
  name <- paste0(current_year, "_Q", current_quarter)
  violence_G_CK_list[[name]] <- G_CK.csr
}
write_rds(violence_G_CK_list, "data/rds/violence_G_CK_list.rds")
par(mar = c(2,1,2,1))
par(mfrow=c(4,4))
# Plotting
for(i in seq_along(violence_G_CK_list)) {
  g_ck <- violence_G_CK_list[[i]]
  plot(g_ck, xlim=c(0,10000), main=names(violence_G_CK_list)[i])
}

5.2.6 Insights

  • Majority of the observed G-function plotted for each quarter for the different events lies above the envelope, suggesting that the points, in this case the conflicts, are more clustered than expected under CSR. This means that the distribution of the conflicts is not at random and tends to cluster in specific regions.

  • The slope of the observed G-function also generally shows a smooth and constant trend.

  • The only exception to the observed trends is the plots for 2021 Q1, which display a more irregular pattern. This irregularity can be attributed to having fewer data points during this time, as the civil war had only just begun in February 2021. When the number of events is smaller, the statistical power of the analysis decreases, leading to less smooth or more erratic G-function patterns. As a result, the G-function for this early quarter may not accurately reflect clustering or dispersion. Given the sparse data from this period, we will not be placing significant weight on the 2021 Q1 results when making decisions about accepting or rejecting the hypothesis.

Conclusion:

  • Based on the analysis, the majority of the observed G-function lies outside the envelope, indicating that the conflict events in Myanmar exhibit a pattern of clustering rather than random distribution. This leads us to reject the null hypothesis that the conflicts are randomly distributed. Instead, the results suggest that conflict events are spatially concentrated in specific regions.

5.3 Hotspot of Conflict

5.3.1 Identifying the Hotspot of Conflict

While the KDE map shows a high concentration of conflict in the region where Sagaing, Magway, and Mandalay intersect, it’s challenging to visually pinpoint the exact region with the highest density of conflict events. To overcome this, we employ a quantitative approach using the following code chunk to calculate the number of conflict events within each region.

By counting the number of data points within each region, we observe that the majority of conflict events occur in the Sagaing region. This result aligns with the spatial trends observed in the KDE map, confirming Sagaing as the area with the highest concentration of conflict.

# Create an empty dataframe to store results
conflict_counts <- data.frame(Region = character(), Conflict_Count = integer(), stringsAsFactors = FALSE)

# Get a list of unique regions
regions <- unique(mmrsr_sf$ST)

# Loop through each region
for (region in regions) {
  # Filter the region geometry
  region_sf <- mmrsr_sf %>% filter(ST == region)
  
  # Perform spatial intersection with conflict points
  no_of_conflict_within <- st_intersection(acled_sf, region_sf)
  
  # Count the number of conflicts in this region
  conflict_count <- nrow(no_of_conflict_within)
  
  # Store the results in the dataframe
  conflict_counts <- rbind(conflict_counts, data.frame(Region = region, Conflict_Count = conflict_count))
}

# Arrange the results in descending order of conflict count
conflict_counts_ordered <- conflict_counts %>%
  arrange(desc(Conflict_Count))

write_rds(conflict_counts_ordered, "data/rds/conflict_counts_ordered.rds")
conflict_counts_ordered <- read_rds( "data/rds/conflict_counts_ordered.rds")
print(conflict_counts_ordered)
         Region Conflict_Count
1       Sagaing          11128
2        Magway           4179
3      Mandalay           3603
4  Shan (North)           2938
5        Kachin           2776
6        Yangon           2608
7       Rakhine           2277
8   Tanintharyi           2240
9         Kayin           1817
10          Mon           1677
11         Chin           1557
12 Shan (South)           1430
13        Kayah           1327
14  Bago (East)           1229
15   Ayeyarwady            852
16  Bago (West)            639
17  Nay Pyi Taw            268
18  Shan (East)             56

5.3.2 Sagaing Region

Given that conflict events in Sagaing account for around 25% of the total, we aim to conduct a more detailed analysis to understand the spatial patterns within this significant region. The first step involves isolating the conflict events specific to Sagaing by filtering the data to focus exclusively on this area. For this, instead of the G-function previously used, we will be using the K-function instead since we are now working with a smaller dataset.

5.3.2.1 Overview of Sagaing Region

Since we are focusing solely on Sagaing, here is a more granular map from MIMU which divides Sagaing into districts so we get a better overview of it.

sagaing_map_sf <- mmrsr_district_sf %>% filter(ST == "Sagaing")
tm_shape(sagaing_map_sf) +
  tm_polygons(col = "DT", palette = colors) +  # Apply color palette to polygons
  tm_text("DT", size = 1, col = "black", bg.color = "white", just = c("center", "center"),  xmod = 0, ymod = 0) + tm_layout(main.title = "Sagaing",
            main.title.position = "center",
            main.title.size = 1.6,
            legend.outside = TRUE,
            frame = TRUE)+
    tm_legend(title = "Districts")  # Set custom legend title

This chunk of code creates a Sagaing owin object, converts acled_sf into a ppp object and plots a map displaying only conflict events in Sagaing Region.

sagaing_owin <- as.owin(sagaing_map_sf)
acled_ppp <- as.ppp(acled_sf)
sagaing_ppp <- acled_ppp[sagaing_owin]
plot(sagaing_ppp, pch = 16, cex = 0.5)

5.3.2.2 Hypothesis

Similar to the one we did for the whole of myanmar, we will conduct a hypothesis test using the K-function along with Monte Carlo simulation envelopes. The test will be designed as follows:

  • H₀: The distribution of conflict events in Sagaing is randomly distributed (follows Complete Spatial Randomness, CSR).

  • H₁: The distribution of conflict events in Sagaing is not randomly distributed.

  • If the observed G-function lies outside the envelope, we will reject the null hypothesis, suggesting the presence of spatial clustering or dispersion.

  • A 95% confidence interval will be used for the envelopes.

5.3.2.3 Battles

set.seed(123456)
battle_data_sf <- grped_acled_sf %>% filter(event_type == "Battles")

# Initialize an empty list to store the results
sagaing_battle_K_CK_list <- list()

# Loop through each combination of year and quarter
for (i in seq_len(nrow(battle_data_sf))) {
  
  # Filter the data for the current iteration
  filtered_sf <- battle_data_sf[i, ]
  
  # Extract the current year, quarter, and event type
  current_year <- filtered_sf$year
  current_quarter <- filtered_sf$quarter
  
  # Convert to ppp object
  filtered_ppp <- as.ppp(st_coordinates(filtered_sf), st_bbox(filtered_sf))
  
  if(any(duplicated(filtered_ppp))){
    filtered_ppp <- rjitter(filtered_ppp, retry=TRUE, nsim=1, drop=TRUE)
  }
  
  filtered_ppp <- filtered_ppp[sagaing_owin]
  K_ck.csr <- envelope(filtered_ppp, Kest, nsim = 99, rank = 1, glocal=TRUE)
  
  # Save the result in the list with a unique name
  name <- paste0(current_year, "_Q", current_quarter)
  sagaing_battle_K_CK_list[[name]] <- K_ck.csr
}

write_rds(sagaing_battle_K_CK_list, "data/rds/sagaing_battle_K_CK_list.rds")
par(mar = c(2,1,2,1))
par(mfrow=c(4,4))
# Plotting
for(i in seq_along(sagaing_battle_K_CK_list)) {
  k_ck <- sagaing_battle_K_CK_list[[i]]
  plot(k_ck, . - r ~ r, xlab="d", ylab="k(d)-r", main=names(sagaing_battle_K_CK_list)[i])
}

5.3.2.4 Explosions/ Remote Violence

set.seed(123456)
explosions_data_sf <- grped_acled_sf %>% filter(event_type == "Explosions/Remote violence")

# Initialize an empty list to store the results
sagaing_explosions_K_CK_list <- list()

# Loop through each combination of year and quarter
for (i in seq_len(nrow(explosions_data_sf))) {
  
  # Filter the data for the current iteration
  filtered_sf <- explosions_data_sf[i, ]
  
  # Extract the current year, quarter, and event type
  current_year <- filtered_sf$year
  current_quarter <- filtered_sf$quarter
  
  # Convert to ppp object
  filtered_ppp <- as.ppp(st_coordinates(filtered_sf), st_bbox(filtered_sf))
  
  if(any(duplicated(filtered_ppp))){
    filtered_ppp <- rjitter(filtered_ppp, retry=TRUE, nsim=1, drop=TRUE)
  }
  
  filtered_ppp <- filtered_ppp[sagaing_owin]
  K_ck.csr <- envelope(filtered_ppp, Kest, nsim = 99, rank = 1, glocal=TRUE)
  
  # Save the result in the list with a unique name
  name <- paste0(current_year, "_Q", current_quarter)
  sagaing_explosions_K_CK_list[[name]] <- K_ck.csr
}

write_rds(sagaing_explosions_K_CK_list, "data/rds/sagaing_explosions_K_CK_list.rds")
par(mar = c(2,1,2,1))
par(mfrow=c(4,4))
# Plotting
for(i in seq_along(sagaing_explosions_K_CK_list)) {
  k_ck <- sagaing_explosions_K_CK_list[[i]]
  plot(k_ck, . - r ~ r, xlab="d", ylab="k(d)-r", main=names(sagaing_explosions_K_CK_list)[i])
}

5.3.2.5 Strategic Developments

set.seed(123456)
strategic_data_sf <- grped_acled_sf %>% filter(event_type == "Strategic developments")

# Initialize an empty list to store the results
sagaing_strategic_K_CK_list <- list()

# Loop through each combination of year and quarter
for (i in seq_len(nrow(strategic_data_sf))) {
  
  # Filter the data for the current iteration
  filtered_sf <- strategic_data_sf[i, ]
  
  # Extract the current year, quarter, and event type
  current_year <- filtered_sf$year
  current_quarter <- filtered_sf$quarter
  
  # Convert to ppp object
  filtered_ppp <- as.ppp(st_coordinates(filtered_sf), st_bbox(filtered_sf))
  
  if(any(duplicated(filtered_ppp))){
    filtered_ppp <- rjitter(filtered_ppp, retry=TRUE, nsim=1, drop=TRUE)
  }
  
  filtered_ppp <- filtered_ppp[sagaing_owin]
  K_ck.csr <- envelope(filtered_ppp, Kest, nsim = 99, rank = 1, glocal=TRUE)
  
  # Save the result in the list with a unique name
  name <- paste0(current_year, "_Q", current_quarter)
  sagaing_strategic_K_CK_list[[name]] <- K_ck.csr
}

write_rds(sagaing_strategic_K_CK_list, "data/rds/sagaing_strategic_K_CK_list.rds")
par(mar = c(2,1,2,1))
par(mfrow=c(4,4))
# Plotting
for(i in seq_along(sagaing_strategic_K_CK_list)) {
  k_ck <- sagaing_strategic_K_CK_list[[i]]
  plot(k_ck, . - r ~ r, xlab="d", ylab="k(d)-r", main=names(sagaing_strategic_K_CK_list)[i])
}

5.3.2.6 Violence Against Civilians

set.seed(123456)
violence_data_sf <- grped_acled_sf %>% filter(event_type == "Violence against civilians")

# Initialize an empty list to store the results
sagaing_violence_K_CK_list <- list()

# Loop through each combination of year and quarter
for (i in seq_len(nrow(violence_data_sf))) {
  
  # Filter the data for the current iteration
  filtered_sf <- violence_data_sf[i, ]
  
  # Extract the current year, quarter, and event type
  current_year <- filtered_sf$year
  current_quarter <- filtered_sf$quarter
  
  # Convert to ppp object
  filtered_ppp <- as.ppp(st_coordinates(filtered_sf), st_bbox(filtered_sf))
  
  if(any(duplicated(filtered_ppp))){
    filtered_ppp <- rjitter(filtered_ppp, retry=TRUE, nsim=1, drop=TRUE)
  }
  
  filtered_ppp <- filtered_ppp[sagaing_owin]
  K_ck.csr <- envelope(filtered_ppp, Kest, nsim = 99, rank = 1, glocal=TRUE)
  
  # Save the result in the list with a unique name
  name <- paste0(current_year, "_Q", current_quarter)
  sagaing_violence_K_CK_list[[name]] <- K_ck.csr
}

write_rds(sagaing_violence_K_CK_list, "data/rds/sagaing_violence_K_CK_list.rds")
par(mar = c(2,1,2,1))
par(mfrow=c(4,4))
# Plotting
for(i in seq_along(sagaing_violence_K_CK_list)) {
  k_ck <- sagaing_violence_K_CK_list[[i]]
  plot(k_ck, . - r ~ r, xlab="d", ylab="k(d)-r", main=names(sagaing_violence_K_CK_list)[i])
}

5.3.2.7 Insights

  • Similar to the G-function plots for Myanmar, we can see that most of the plots are identical with majority of the observed K-function being outside and above the envelope with constant and smooth trends.

  • The smooth trend of the K-function across the plots implies that the clustering of events is not only prevalent but also persistent over time.

  • The only exception to this is yet again 2021 q1, this time we can also observe a stepped pattern with the plots which again is likely attributed to the small amount of data points during this period of time, and hence we will not be placing significant weight on the 2021 Q1 results when making decisions about accepting or rejecting the hypothesis.

  • Since the observed K-function are all above the envelope, we can reject the null hypothesis that conflict events in Sagaing is randomly distributed and instead conclude that they are clustered at specific regions. Observing the point pattern map we previously plotted, we can see that most conflict events occur in the following districts: Kanbalu, Shwebo, Sagaing (District, not region), Monywa, Yinmarbi.

6. Quarterly Spatio-Temporal KDE Layers

In this section, we will be conducting spatiotemporal Kernel Density Estimation (KDE). This approach allows us to visualize and analyze the density of conflicts over both space and time, providing a comprehensive view of how conflict activity evolves. By applying KDE to the dataset spanning from 2021 to 2024, we can identify hotspots and trends in conflict intensity across different quarters and regions.

6.1 Overview

This chunk of code utilizes spattemp.density() to compute the spatiotemporal KDE for all 14 quarters from 2021 to 2024 Q2.

Click to view the code
  par(mar = c(2,0,2,0))
  par(mfrow=c(4,4))
  current_event = "Battles"
  
  # Filter the data
  battle_data_sf <- grped_acled_sf %>% filter(event_type == current_event)
  
  # Define time slices for plotting
  tims <- c(
  "2021 Q1", "2021 Q2", "2021 Q3", "2021 Q4",
  "2022 Q1", "2022 Q2", "2022 Q3", "2022 Q4",
  "2023 Q1", "2023 Q2", "2023 Q3", "2023 Q4",
  "2024 Q1", "2024 Q2"
  )
  
  # Convert to ppp object
  spattemp_ppp <- as.ppp(st_coordinates(battle_data_sf), st_bbox(battle_data_sf))
  # Remove Duplicates
  if(any(duplicated(spattemp_ppp))){
    rjitter(spattemp_ppp, retry=TRUE, nsim=1, drop=TRUE)
  }
  
  # Convert to owin
  spattemp_ppp <- spattemp_ppp[mmr_owin]

  # Compute the spatiotemporal Kernel Density Estimate (KDE)
  st_kde <- spattemp.density(spattemp_ppp)
  for(x in seq_along(tims)){
    plot(st_kde, x, 
     override.par=FALSE, 
     fix.range=TRUE, 
     main=paste(tims[x]))
}

Click to view the code
  par(mar = c(2,0,2,0))
  par(mfrow=c(4,4))
  current_event = "Explosions/Remote violence"

  # Filter the data
  explosions_data_sf <- grped_acled_sf %>% filter(event_type == current_event)
  
  # Define time slices for plotting
  tims <- c(
  "2021 Q1", "2021 Q2", "2021 Q3", "2021 Q4",
  "2022 Q1", "2022 Q2", "2022 Q3", "2022 Q4",
  "2023 Q1", "2023 Q2", "2023 Q3", "2023 Q4",
  "2024 Q1", "2024 Q2"
)
  
  # Convert to ppp object
  spattemp_ppp <- as.ppp(st_coordinates(explosions_data_sf), st_bbox(explosions_data_sf))
  # Remove Duplicates
  if(any(duplicated(spattemp_ppp))){
    rjitter(spattemp_ppp, retry=TRUE, nsim=1, drop=TRUE)
  }
  # Convert to owin
  spattemp_ppp <- spattemp_ppp[mmr_owin]
  
  # Compute the spatiotemporal Kernel Density Estimate (KDE)
  st_kde <- spattemp.density(spattemp_ppp)
  for(x in seq_along(tims)){
    plot(st_kde, x, 
     override.par=FALSE, 
     fix.range=TRUE, 
     main=paste(tims[x]))
}

Click to view the code
  par(mar = c(2,0,2,0))
  par(mfrow=c(4,4))
  current_event = "Strategic developments"
  
  # Filter the data
  strategic_data_sf <- grped_acled_sf %>% filter(event_type == current_event)
  
  # Define time slices for plotting
  tims <- c(
  "2021 Q1", "2021 Q2", "2021 Q3", "2021 Q4",
  "2022 Q1", "2022 Q2", "2022 Q3", "2022 Q4",
  "2023 Q1", "2023 Q2", "2023 Q3", "2023 Q4",
  "2024 Q1", "2024 Q2"
)
  
  # Convert to ppp object
  spattemp_ppp <- as.ppp(st_coordinates(strategic_data_sf), st_bbox(strategic_data_sf))
  # Remove Duplicates
  if(any(duplicated(spattemp_ppp))){
    rjitter(spattemp_ppp, retry=TRUE, nsim=1, drop=TRUE)
  }
  # Convert to owin
  spattemp_ppp <- spattemp_ppp[mmr_owin]
  
  # Compute the spatiotemporal Kernel Density Estimate (KDE)
  st_kde <- spattemp.density(spattemp_ppp)
  for(x in seq_along(tims)){
    plot(st_kde, x, 
     override.par=FALSE, 
     fix.range=TRUE, 
     main=paste(tims[x]))
}

Click to view the code
  par(mar = c(2,0,2,0))
  par(mfrow=c(4,4))
  current_event = "Violence against civilians"
  # Filter the data
  violence_data_sf <- grped_acled_sf %>% filter(event_type == current_event)
  
  # Define time slices for plotting
  tims <- c(
  "2021 Q1", "2021 Q2", "2021 Q3", "2021 Q4",
  "2022 Q1", "2022 Q2", "2022 Q3", "2022 Q4",
  "2023 Q1", "2023 Q2", "2023 Q3", "2023 Q4",
  "2024 Q1", "2024 Q2"
)
  
  # Convert to ppp object
  spattemp_ppp <- as.ppp(st_coordinates(violence_data_sf), st_bbox(violence_data_sf))
  # Remove Duplicates
  if(any(duplicated(spattemp_ppp))){
    rjitter(spattemp_ppp, retry=TRUE, nsim=1, drop=TRUE)
  }
  # Convert to owin
  spattemp_ppp <- spattemp_ppp[mmr_owin]
  
  # Compute the spatiotemporal Kernel Density Estimate (KDE)
  st_kde <- spattemp.density(spattemp_ppp)
  for(x in seq_along(tims)){
    plot(st_kde, x, 
     override.par=FALSE, 
     fix.range=TRUE, 
     main=paste(tims[x]))
}

6.2 Spatio-Temporal KDE By Year

6.2.1 Battles

  current_event = "Battles"
  quarter_2021_sf <- grped_acled_sf %>% filter(event_type == current_event, year == 2021)
  quarter_2022_sf <- grped_acled_sf %>% filter(event_type == current_event, year == 2022)
  quarter_2023_sf <- grped_acled_sf %>% filter(event_type == current_event, year == 2023)
  quarter_2024_sf <- grped_acled_sf %>% filter(event_type == current_event, year == 2024)

This chunk of code plots the Spatio-Temporal KDE Layers by year.

Click to view the code
  par(mar = c(2,0,2,0))
  par(mfrow=c(2,2))
  year = 2021
  
  tims <- c("Q1", "Q2", "Q3", "Q4")
  # Convert to ppp object
  spattemp_ppp <- as.ppp(st_coordinates(quarter_2021_sf), st_bbox(quarter_2021_sf))
  # Remove Duplicates
  if(any(duplicated(spattemp_ppp))){
    rjitter(spattemp_ppp, retry=TRUE, nsim=1, drop=TRUE)
  }
  
  # Convert to owin
  spattemp_ppp <- spattemp_ppp[mmr_owin]
  
  # Compute the spatiotemporal Kernel Density Estimate (KDE)
  st_kde <- spattemp.density(spattemp_ppp)
  for(x in seq_along(tims)){
    plot(st_kde, x, 
     override.par=FALSE, 
     fix.range=TRUE, 
     main=paste(year,tims[x]))}

Click to view the code
  par(mar = c(2,0,2,0))
  par(mfrow=c(2,2))
  year = 2022
  
  tims <- c("Q1", "Q2", "Q3", "Q4")
  # Convert to ppp object
  spattemp_ppp <- as.ppp(st_coordinates(quarter_2022_sf), st_bbox(quarter_2022_sf))
  # Remove Duplicates
  if(any(duplicated(spattemp_ppp))){
    rjitter(spattemp_ppp, retry=TRUE, nsim=1, drop=TRUE)
  }
  
  # Convert to owin
  spattemp_ppp <- spattemp_ppp[mmr_owin]
  
  # Compute the spatiotemporal Kernel Density Estimate (KDE)
  st_kde <- spattemp.density(spattemp_ppp)
  for(x in seq_along(tims)){
    plot(st_kde, x, 
     override.par=FALSE, 
     fix.range=TRUE, 
     main=paste(year,tims[x]))}

Click to view the code
  par(mar = c(2,0,2,0))
  par(mfrow=c(2,2))
  year = 2023
  
  tims <- c("Q1", "Q2", "Q3", "Q4")
  # Convert to ppp object
  spattemp_ppp <- as.ppp(st_coordinates(quarter_2023_sf), st_bbox(quarter_2023_sf))
  # Remove Duplicates
  if(any(duplicated(spattemp_ppp))){
    rjitter(spattemp_ppp, retry=TRUE, nsim=1, drop=TRUE)
  }
  
  # Convert to owin
  spattemp_ppp <- spattemp_ppp[mmr_owin]
  
  # Compute the spatiotemporal Kernel Density Estimate (KDE)
  st_kde <- spattemp.density(spattemp_ppp)
  for(x in seq_along(tims)){
    plot(st_kde, x, 
     override.par=FALSE, 
     fix.range=TRUE, 
     main=paste(year,tims[x]))}

Click to view the code
  par(mar = c(2,0,2,0))
  par(mfrow=c(2,2))
  year = 2024
  
  tims <- c("Q1", "Q2")
  # Convert to ppp object
  spattemp_ppp <- as.ppp(st_coordinates(quarter_2024_sf), st_bbox(quarter_2024_sf))
  # Remove Duplicates
  if(any(duplicated(spattemp_ppp))){
    rjitter(spattemp_ppp, retry=TRUE, nsim=1, drop=TRUE)
  }
  
  # Convert to owin
  spattemp_ppp <- spattemp_ppp[mmr_owin]
  
  # Compute the spatiotemporal Kernel Density Estimate (KDE)
  st_kde <- spattemp.density(spattemp_ppp)
  for(x in seq_along(tims)){
    plot(st_kde, x, 
     override.par=FALSE, 
     fix.range=TRUE, 
     main=paste(year,tims[x]))}

Note
  • In early 2021, event density was relatively sparse, with small clusters emerging in central Myanmar.

  • By Q4 of 2021, the density intensified, revealing a significant hotspot in central Myanmar, indicating escalated conflict.

  • Throughout 2022 and 2023, the central region remained the most active, though the extent and concentration of hotspots fluctuated, suggesting either an escalation or a shift in conflict zones.

  • In 2024, the pattern became very dispersed, indicating further changes in conflict dynamics.

6.2.2 Explosions/Remote violence

  current_event = "Explosions/Remote violence"
  quarter_2021_sf <- grped_acled_sf %>% filter(event_type == current_event, year == 2021)
  quarter_2022_sf <- grped_acled_sf %>% filter(event_type == current_event, year == 2022)
  quarter_2023_sf <- grped_acled_sf %>% filter(event_type == current_event, year == 2023)
  quarter_2024_sf <- grped_acled_sf %>% filter(event_type == current_event, year == 2024)
Click to view the code
  par(mar = c(2,0,2,0))
  par(mfrow=c(2,2))
  year = 2021
  
  tims <- c("Q1", "Q2", "Q3", "Q4")
  # Convert to ppp object
  spattemp_ppp <- as.ppp(st_coordinates(quarter_2021_sf), st_bbox(quarter_2021_sf))
  # Remove Duplicates
  if(any(duplicated(spattemp_ppp))){
    rjitter(spattemp_ppp, retry=TRUE, nsim=1, drop=TRUE)
  }
  
  # Convert to owin
  spattemp_ppp <- spattemp_ppp[mmr_owin]
  
  # Compute the spatiotemporal Kernel Density Estimate (KDE)
  st_kde <- spattemp.density(spattemp_ppp)
  for(x in seq_along(tims)){
    plot(st_kde, x, 
     override.par=FALSE, 
     fix.range=TRUE, 
     main=paste(year,tims[x]))}

Click to view the code
  par(mar = c(2,0,2,0))
  par(mfrow=c(2,2))
  year = 2022
  
  tims <- c("Q1", "Q2", "Q3", "Q4")
  # Convert to ppp object
  spattemp_ppp <- as.ppp(st_coordinates(quarter_2022_sf), st_bbox(quarter_2022_sf))
  # Remove Duplicates
  if(any(duplicated(spattemp_ppp))){
    rjitter(spattemp_ppp, retry=TRUE, nsim=1, drop=TRUE)
  }
  
  # Convert to owin
  spattemp_ppp <- spattemp_ppp[mmr_owin]
  
  # Compute the spatiotemporal Kernel Density Estimate (KDE)
  st_kde <- spattemp.density(spattemp_ppp)
  for(x in seq_along(tims)){
    plot(st_kde, x, 
     override.par=FALSE, 
     fix.range=TRUE, 
     main=paste(year,tims[x]))}

Click to view the code
  par(mar = c(2,0,2,0))
  par(mfrow=c(2,2))
  year = 2023
  
  tims <- c("Q1", "Q2", "Q3", "Q4")
  # Convert to ppp object
  spattemp_ppp <- as.ppp(st_coordinates(quarter_2023_sf), st_bbox(quarter_2023_sf))
  # Remove Duplicates
  if(any(duplicated(spattemp_ppp))){
    rjitter(spattemp_ppp, retry=TRUE, nsim=1, drop=TRUE)
  }
  
  # Convert to owin
  spattemp_ppp <- spattemp_ppp[mmr_owin]
  
  # Compute the spatiotemporal Kernel Density Estimate (KDE)
  st_kde <- spattemp.density(spattemp_ppp)
  for(x in seq_along(tims)){
    plot(st_kde, x, 
     override.par=FALSE, 
     fix.range=TRUE, 
     main=paste(year,tims[x]))}

Click to view the code
  par(mar = c(2,0,2,0))
  par(mfrow=c(2,2))
  year = 2024
  
  tims <- c("Q1", "Q2")
  # Convert to ppp object
  spattemp_ppp <- as.ppp(st_coordinates(quarter_2024_sf), st_bbox(quarter_2024_sf))
  # Remove Duplicates
  if(any(duplicated(spattemp_ppp))){
    rjitter(spattemp_ppp, retry=TRUE, nsim=1, drop=TRUE)
  }
  
  # Convert to owin
  spattemp_ppp <- spattemp_ppp[mmr_owin]
  
  # Compute the spatiotemporal Kernel Density Estimate (KDE)
  st_kde <- spattemp.density(spattemp_ppp)
  for(x in seq_along(tims)){
    plot(st_kde, x, 
     override.par=FALSE, 
     fix.range=TRUE, 
     main=paste(year,tims[x]))}

Note
  • In both 2022 and 2023, the Sagaing region of Myanmar experienced similar significant fluctuations in event density:

    • Q1: There shows to be a high density of explosions/remote violence in the Sagaing region.

    • Q2 & Q3: Conflicts subsided, likely due to localized negotiations and strategic withdrawals by armed groups, which reduced hostilities.

    • Q4: Conflicts yet again intensified, surpassing even Q1. A resurgence of conflict seems to have occurred.

  • In Q4 of 2023, conflicts exhibited a more dispersed pattern, primarily concentrated in the northern region, with Sagaing remaining at the center of this activity.

6.2.3 Strategic developments

  current_event = "Strategic developments"
  quarter_2021_sf <- grped_acled_sf %>% filter(event_type == current_event, year == 2021)
  quarter_2022_sf <- grped_acled_sf %>% filter(event_type == current_event, year == 2022)
  quarter_2023_sf <- grped_acled_sf %>% filter(event_type == current_event, year == 2023)
  quarter_2024_sf <- grped_acled_sf %>% filter(event_type == current_event, year == 2024)
Click to view the code
  par(mar = c(2,0,2,0))
  par(mfrow=c(2,2))
  year = 2021
  
  tims <- c("Q1", "Q2", "Q3", "Q4")
  # Convert to ppp object
  spattemp_ppp <- as.ppp(st_coordinates(quarter_2021_sf), st_bbox(quarter_2021_sf))
  # Remove Duplicates
  if(any(duplicated(spattemp_ppp))){
    rjitter(spattemp_ppp, retry=TRUE, nsim=1, drop=TRUE)
  }
  
  # Convert to owin
  spattemp_ppp <- spattemp_ppp[mmr_owin]
  
  # Compute the spatiotemporal Kernel Density Estimate (KDE)
  st_kde <- spattemp.density(spattemp_ppp)
  for(x in seq_along(tims)){
    plot(st_kde, x, 
     override.par=FALSE, 
     fix.range=TRUE, 
     main=paste(year,tims[x]))}

Click to view the code
  par(mar = c(2,0,2,0))
  par(mfrow=c(2,2))
  year = 2022
  
  tims <- c("Q1", "Q2", "Q3", "Q4")
  # Convert to ppp object
  spattemp_ppp <- as.ppp(st_coordinates(quarter_2022_sf), st_bbox(quarter_2022_sf))
  # Remove Duplicates
  if(any(duplicated(spattemp_ppp))){
    rjitter(spattemp_ppp, retry=TRUE, nsim=1, drop=TRUE)
  }
  
  # Convert to owin
  spattemp_ppp <- spattemp_ppp[mmr_owin]
  
  # Compute the spatiotemporal Kernel Density Estimate (KDE)
  st_kde <- spattemp.density(spattemp_ppp)
  for(x in seq_along(tims)){
    plot(st_kde, x, 
     override.par=FALSE, 
     fix.range=TRUE, 
     main=paste(year,tims[x]))}

Click to view the code
  par(mar = c(2,0,2,0))
  par(mfrow=c(2,2))
  year = 2023
  
  tims <- c("Q1", "Q2", "Q3", "Q4")
  # Convert to ppp object
  spattemp_ppp <- as.ppp(st_coordinates(quarter_2023_sf), st_bbox(quarter_2023_sf))
  # Remove Duplicates
  if(any(duplicated(spattemp_ppp))){
    rjitter(spattemp_ppp, retry=TRUE, nsim=1, drop=TRUE)
  }
  
  # Convert to owin
  spattemp_ppp <- spattemp_ppp[mmr_owin]
  
  # Compute the spatiotemporal Kernel Density Estimate (KDE)
  st_kde <- spattemp.density(spattemp_ppp)
  for(x in seq_along(tims)){
    plot(st_kde, x, 
     override.par=FALSE, 
     fix.range=TRUE, 
     main=paste(year,tims[x]))}

Click to view the code
  par(mar = c(2,0,2,0))
  par(mfrow=c(2,2))
  year = 2024
  
  tims <- c("Q1", "Q2")
  # Convert to ppp object
  spattemp_ppp <- as.ppp(st_coordinates(quarter_2024_sf), st_bbox(quarter_2024_sf))
  # Remove Duplicates
  if(any(duplicated(spattemp_ppp))){
    rjitter(spattemp_ppp, retry=TRUE, nsim=1, drop=TRUE)
  }
  
  # Convert to owin
  spattemp_ppp <- spattemp_ppp[mmr_owin]
  
  # Compute the spatiotemporal Kernel Density Estimate (KDE)
  st_kde <- spattemp.density(spattemp_ppp)
  for(x in seq_along(tims)){
    plot(st_kde, x, 
     override.par=FALSE, 
     fix.range=TRUE, 
     main=paste(year,tims[x]))}

Note

During Q2 to Q4 of 2023, there was limited activity in strategic developments, but this changed abruptly with a significant intensification in Q1 of 2024.

6.2.4 Violence against civilians

  current_event = "Violence against civilians"
  quarter_2021_sf <- grped_acled_sf %>% filter(event_type == current_event, year == 2021)
  quarter_2022_sf <- grped_acled_sf %>% filter(event_type == current_event, year == 2022)
  quarter_2023_sf <- grped_acled_sf %>% filter(event_type == current_event, year == 2023)
  quarter_2024_sf <- grped_acled_sf %>% filter(event_type == current_event, year == 2024)
Click to view the code
  par(mar = c(2,0,2,0))
  par(mfrow=c(2,2))
  year = 2021
  
  tims <- c("Q1", "Q2", "Q3", "Q4")
  # Convert to ppp object
  spattemp_ppp <- as.ppp(st_coordinates(quarter_2021_sf), st_bbox(quarter_2021_sf))
  # Remove Duplicates
  if(any(duplicated(spattemp_ppp))){
    rjitter(spattemp_ppp, retry=TRUE, nsim=1, drop=TRUE)
  }
  
  # Convert to owin
  spattemp_ppp <- spattemp_ppp[mmr_owin]
  
  # Compute the spatiotemporal Kernel Density Estimate (KDE)
  st_kde <- spattemp.density(spattemp_ppp)
  for(x in seq_along(tims)){
    plot(st_kde, x, 
     override.par=FALSE, 
     fix.range=TRUE, 
     main=paste(year,tims[x]))}

Click to view the code
  par(mar = c(2,0,2,0))
  par(mfrow=c(2,2))
  year = 2022
  
  tims <- c("Q1", "Q2", "Q3", "Q4")
  # Convert to ppp object
  spattemp_ppp <- as.ppp(st_coordinates(quarter_2022_sf), st_bbox(quarter_2022_sf))
  # Remove Duplicates
  if(any(duplicated(spattemp_ppp))){
    rjitter(spattemp_ppp, retry=TRUE, nsim=1, drop=TRUE)
  }
  
  # Convert to owin
  spattemp_ppp <- spattemp_ppp[mmr_owin]
  
  # Compute the spatiotemporal Kernel Density Estimate (KDE)
  st_kde <- spattemp.density(spattemp_ppp)
  for(x in seq_along(tims)){
    plot(st_kde, x, 
     override.par=FALSE, 
     fix.range=TRUE, 
     main=paste(year,tims[x]))}

Click to view the code
  par(mar = c(2,0,2,0))
  par(mfrow=c(2,2))
  year = 2023
  
  tims <- c("Q1", "Q2", "Q3", "Q4")
  # Convert to ppp object
  spattemp_ppp <- as.ppp(st_coordinates(quarter_2023_sf), st_bbox(quarter_2023_sf))
  # Remove Duplicates
  if(any(duplicated(spattemp_ppp))){
    rjitter(spattemp_ppp, retry=TRUE, nsim=1, drop=TRUE)
  }
  
  # Convert to owin
  spattemp_ppp <- spattemp_ppp[mmr_owin]
  
  # Compute the spatiotemporal Kernel Density Estimate (KDE)
  st_kde <- spattemp.density(spattemp_ppp)
  for(x in seq_along(tims)){
    plot(st_kde, x, 
     override.par=FALSE, 
     fix.range=TRUE, 
     main=paste(year,tims[x]))}

Click to view the code
  par(mar = c(2,0,2,0))
  par(mfrow=c(2,2))
  year = 2024
  
  tims <- c("Q1", "Q2")
  # Convert to ppp object
  spattemp_ppp <- as.ppp(st_coordinates(quarter_2024_sf), st_bbox(quarter_2024_sf))
  # Remove Duplicates
  if(any(duplicated(spattemp_ppp))){
    rjitter(spattemp_ppp, retry=TRUE, nsim=1, drop=TRUE)
  }
  
  # Convert to owin
  spattemp_ppp <- spattemp_ppp[mmr_owin]
  
  # Compute the spatiotemporal Kernel Density Estimate (KDE)
  st_kde <- spattemp.density(spattemp_ppp)
  for(x in seq_along(tims)){
    plot(st_kde, x, 
     override.par=FALSE, 
     fix.range=TRUE, 
     main=paste(year,tims[x]))}

Note

In 2024, incidents of violence against civilians became more dispersed, featuring a significant hotspot of high density in the Sagaing region, there also appears to be some concentration of activity in southern Myanmar that was not commonly observed in other plots.

7. 2nd-Order Spatio-temporal Point Patterns Analysis

The specific questions we aim to address are whether the locations of conflict in Myanmar are spatio-temporally independent. Using second-order spatio-temporal point pattern analysis, we will examine if the conflicts exhibit significant clustering or dispersion across geographic areas and over time.

This section is definitely the toughest for me as it was hard to find resources and documentations online to perform the relevant analysis.

7.1 Learning Process

7.1.1 Starting Out

To start off, I researched on the potential Second Order Spatio-temporal Point Patterns Analysis methods I could use and ended up shortlisting out these options:

  • Spatio-temporal K-function: This function extends the traditional K-function to consider both space and time, allowing us to assess how conflicts cluster over time and across different locations.

  • Ripley’s K-function: Specifically, the K-function can be adapted for spatio-temporal analysis to evaluate the density of events in both dimensions. This helps in identifying patterns of clustering or dispersion over specified time intervals.

  • Log-Gaussian Cox Process (LGCP): This is a flexible model that accounts for spatial dependence in point patterns and allows for varying intensity across space and time. LGCP can help in modeling the intensity of conflict events as a function of spatial covariates, providing insights into the underlying processes driving the distribution of conflicts.

Instinctively, I was inclined to stick with the K-function since it is a method I am familiar with and have applied before, making it a comfortable starting point for my analysis. However, I also recognized that the term “Log-Gaussian Cox Process” appeared frequently in the literature I reviewed, which prompted me to include it in my shortlist.

7.1.2 Failed Attempts

Despite trying many different functions and methods, not many of them came through. A method that I had initially thought worked with the help of ChatGPT was using time (in this case quarter number) to mark the point patterns. However, I soon realised after that the standard Kest function that we used in class does not seem to take into account marks.

Using Mark for K-Function

current_event <- "Battles"

quarter_to_continuous <- function(year, quarter) {
  # Calculate the number of quarters since a reference point (e.g., 2000 Q1)
  reference_year <- 2021
  quarters_since_reference <- (year - reference_year) * 4 + quarter
  return(quarters_since_reference)
}

grped_acled_quartered_sf <- acled_sf %>%   mutate(
                                                          year = as.numeric(year),
                                                          quarter = as.numeric(quarter),
                                                          t = quarter_to_continuous(year, quarter)
                                                        )
grped_acled_quartered_sf <- grped_acled_quartered_sf %>% filter(event_type == current_event)

# Step 2: Convert the data to a spatio-temporal point pattern
# Define the spatial window (using your bounding box)

# Create a spatio-temporal point pattern (ppp is the spatial part, time is added separately)
ppp_data <- as.ppp(st_coordinates(grped_acled_quartered_sf), W = mmr_owin)
marks(ppp_data) <- data.frame(time = acled_sf$t)

Kst_result <- Kest(ppp_data, tmax = 1, rmax = 10000)

# Step 4: Visualize and interpret the results
plot(Kst_result)

7.2 Space-Time Inhomogeneous K-function

The method that I eventually ended up going for, and succeeding in getting to actually work (or at least have some semblance of success) is using STIKhat() from the package stpp which is said to be designed for “Space-Time Point Pattern Simulation, Visualisation and Analysis”.

STIKhat(): Compute an estimate of the Space-Time Inhomogeneous K-function.

This chunk of code converts acled data to continuous quarter for time slices and gets rid of duplicate rows with the same event type, geometry and t (overall quarter no, e.g. 2024 Q2 –> 14).

  quarter_to_continuous <- function(year, quarter) {
    # Calculate the number of quarters since a reference point (e.g., 2000 Q1)
    reference_year <- 2021
    quarters_since_reference <- (year - reference_year) * 4 + quarter
    return(quarters_since_reference)
  }
  
  grped_acled_quartered_sf <- acled_sf %>%   mutate(year = as.numeric(year),
                                                    quarter = as.numeric(quarter),
                                                    t = quarter_to_continuous(year, quarter)
                                                    ) %>% distinct(event_type, geometry, t, .keep_all = TRUE)
Important

There seems to be an issue where the plots appear in the viewer of Rstudio but yet I am unable to have it rendered and appear on the Quarto Document. Hence I have made the choice to instead download and display it instead.

This first chunk of code utilises STIKhat() from the stpp package to compute an estimate of the Space-Time Inhomogeneous K-function and plotK() to plot the graph.

The second chunk of code then plots the different graphical representation of the estimate of the Space-Time Inhomogeneous K-function: Projection, Perspective, Image so that we are able to get a view of the different graphs.

Click to view the code
  current_event <- "Battles"

  battle_quartered_sf <- grped_acled_quartered_sf %>% filter(event_type == current_event)
  coords_matrix <- st_coordinates(battle_quartered_sf)
  x_coords <- coords_matrix[, "X"]
  y_coords <- coords_matrix[, "Y"]
  
  stpp_data <- as.3dpoints(x_coords,y_coords, battle_quartered_sf$t)
  
  Kst_result <- stpp::STIKhat(xyt=stpp_data)

  write_rds(Kst_result, "data/rds/battle_stik_result.rds")
plotK(battle_stik_result)
plotK(battle_stik_result, type ="persp")
plotK(battle_stik_result, type ="image")

Battle Projection

Battle Perspective

Battle Image
Click to view the code
  current_event <- "Explosions/Remote violence"

  explosions_quartered_sf <- grped_acled_quartered_sf %>% filter(event_type == current_event)
  
  coords_matrix <- st_coordinates(explosions_quartered_sf)
  x_coords <- coords_matrix[, "X"]
  y_coords <- coords_matrix[, "Y"]
  
  stpp_data <- as.3dpoints(x_coords,y_coords, explosions_quartered_sf$t)
  
  Kst_result <- stpp::STIKhat(xyt=stpp_data)

  write_rds(Kst_result, "data/rds/explosions_stik_result.rds")
plotK(explosions_stik_result)
plotK(explosions_stik_result, type ="persp")
plotK(explosions_stik_result, type ="image")

Explosions Projection

Explosions Perspective

Explosions Image
Click to view the code
  current_event <- "Strategic developments"

  strategic_quartered_sf <- grped_acled_quartered_sf %>% filter(event_type == current_event)
  
  coords_matrix <- st_coordinates(strategic_quartered_sf)
  x_coords <- coords_matrix[, "X"]
  y_coords <- coords_matrix[, "Y"]
  
  stpp_data <- as.3dpoints(x_coords,y_coords, strategic_quartered_sf$t)
  
  Kst_result <- stpp::STIKhat(xyt=stpp_data)

  write_rds(Kst_result, "data/rds/strategic_stik_result.rds")
plotK(strategic_stik_result)
plotK(strategic_stik_result, type ="persp")
plotK(strategic_stik_result, type ="image")

Strategic Projection

Strategic Perspective

Strategic Image
Click to view the code
  current_event <- "Violence against civilians"
  violence_quartered_sf <- grped_acled_quartered_sf %>% filter(event_type == current_event)
  
  coords_matrix <- st_coordinates(violence_quartered_sf)
  x_coords <- coords_matrix[, "X"]
  y_coords <- coords_matrix[, "Y"]
  
  stpp_data <- as.3dpoints(x_coords,y_coords, violence_quartered_sf$t)
  
  Kst_result <- stpp::STIKhat(xyt=stpp_data)

  write_rds(Kst_result, "data/rds/violence_stik_result.rds")
plotK(violence_stik_result)
plotK(violence_stik_result, type ="persp")
plotK(violence_stik_result, type ="image")

Violence Projection

Violence Perspective

Violence Image

7.3 Interpreting

Difference between the plots

  • The projection plot shows contours of intensity over space and time.

  • The image plot provides a heatmap view, with darker blue indicating higher intensity.

  • The perspective plot gives a 3D visualization of the intensity surface.

Overall Insights

  • All four types of event show spatiotemporal clustering, indicating non-random patterns.

  • There’s a general trend of increasing intensity over time for all event types, which could suggest an escalation of conflict.

  • The similar intensity scales across all event types suggest that no single type of event dominates the conflict in terms of frequency or intensity.

8. Overall Insights

In conclusion, the analysis of the Myanmar conflict from 2021 to 2024 reveals significant shifts in patterns of violence, initially marked by fluidity and later stabilizing in key regions such as Southern Sagaing. Both the Kernel Density Estimation (KDE) and spatio-temporal analyses consistently indicate clustering of conflict events, as demonstrated by the G-function and K-function results.

Importantly, the KDE map illustrates a correlation between “Strategic Developments” and “Violence Against Civilians,” as evidenced by their similar spatial distributions. The sharp rise in strategic developments in Q1 2024, coupled with the emergence of new hotspots for violence against civilians, underscores the ongoing changes in conflict dynamics.

These findings highlight the urgent need for targeted interventions in entrenched conflict zones, particularly in Southern Sagaing and the newly identified hotspots in southern Myanmar. As the situation continues to evolve, addressing these areas will be crucial for mitigating violence and fostering stability.

9. Reflections

The biggest challenge I faced in this assignment was interpreting the plots and analyses. While there are many technical guides on calculating spatial statistics, few provide clear explanations on how to interpret the generated plots. It took hours of research to find suitable resources, and information on STIKhat, in particular, was quite limited. I cannot yet confidently say that I fully understand what it aims to show or how to interpret it.

Distinguishing between statistical significance and practical relevance was also complex. Although my results showed clear clustering, translating this into meaningful insights—such as understanding the factors driving increased conflict in certain areas—proved challenging. While grappling with the G-function and K-function provided valuable insights, it also made me question the appropriateness of these models for analyzing phenomena where conflicts are rarely random.

Ultimately, this assignment reinforced the necessity of not only relying on statistical results but also developing the ability to interpret and contextualize those findings within real-world contexts. Nonetheless, I learned a lot from this experience, and I consider my greatest accomplishment to be mastering the interpretation of the G-function and K-function plots. Overall, it was a truly enriching experience and I learnt a lot.

10. References

Baddeley, A., Rubak, E., & Turner, R. (2016). Spatial point patterns: Methodology and applications with R. CHAPMAN & HALL CRC.